How can I spilt a varchar column to different columns - sql

I have a database table that holds userspecified data for customer orders.
instead of making a column per custom field the wrighter of the software made a 3 column system like this:
orderline_ID Field_ID Value
--------------------------------
1 1 50
1 2 today
1 3 green
2 1 80
2 2 next week
2 3 60
I want this data sorted like this:
Orderline_ID 1 2 3
----------------------------------------
1 50 today green
2 80 next week 60
so I can join it in an other query I use.
But the code I wrote came up like
Orderline_ID 1 2 3
-----------------------------------------
1 50 NULL NULL
1 NULL today NULL
1 NULL NULL green
2 80 NULL NULL
2 NULL next week NULL
2 NULL NULL 60
and when I sort by Orderline_ID it results in a error.
The code I used:
SELECT
fldVerkoopOrderRegelID,
(SELECT VOG.fldWaarde
WHERE (VOG.fldVeldNummer = 1) AND (VOG.fldWaarde IS NOT NULL)) AS [aantal vaten],
(SELECT VOG.fldWaarde
WHERE (VOG.fldVeldNummer = 2) AND (VOG.fldWaarde IS NOT NULL)) AS [Vat nett0],
(SELECT VOG.fldWaarde
WHERE (VOG.fldVeldNummer = 3) AND (VOG.fldWaarde IS NOT NULL)) AS [Vat bruto],
(SELECT VOG.fldWaarde
WHERE (VOG.fldVeldNummer = 4) AND (VOG.fldWaarde IS NOT NULL)) AS [cust product code],
(SELECT VOG.fldWaarde
WHERE (VOG.fldVeldNummer = 5) AND (VOG.fldWaarde IS NOT NULL)) AS [extra text],
(SELECT VOG.fldWaarde
WHERE (VOG.fldVeldNummer = 6) AND (VOG.fldWaarde IS NOT NULL)) AS [HS code]
FROM
dbo.tblVerkoopOrderIngaveGegeven AS VOG
WHERE
(fldVerkoopOrderRegelID IS NOT NULL)

this achievable using left join.
select t1.orderline_id, t1.Value, t2.Value, t3.Value
from tblVerkoopOrderIngaveGegeven t1
left join tblVerkoopOrderIngaveGegeven t2 on t2.orderline_id = t1.orderline_id and t2.field_id = 2
left join tblVerkoopOrderIngaveGegeven t3 on t3.orderline_id = t1.orderline_id and t3.field_id = 3
where t1.field_id = 1

You can select distinct a unique order ids and then do a left join on three tables that each has the column you need i.e. 1,2,3
DECLARE #Orders TABLE (
[Orderline_ID] INT,
[Field_ID] INT,
[Value] VARCHAR(MAX)
)
INSERT INTO #Orders SELECT 1, 1, '50'
INSERT INTO #Orders SELECT 1, 2, 'today'
INSERT INTO #Orders SELECT 1, 3, 'green'
INSERT INTO #Orders SELECT 2, 1, '80'
INSERT INTO #Orders SELECT 2, 2, 'next week'
INSERT INTO #Orders SELECT 2, 3, '60'
SELECT
[T].[Orderline_ID],
[T1].[C1],
[T2].[C2],
[T3].[C3]
FROM
(SELECT DISTINCT [Orderline_ID] FROM #Orders ) AS [T]
LEFT JOIN (SELECT [Orderline_ID], [Field_ID], [Value] AS [C1] FROM #Orders) AS [T1] ON ([T].[Orderline_ID] = [T1].[Orderline_ID] AND [T1].[Field_ID] = 1)
LEFT JOIN (SELECT [Orderline_ID], [Field_ID], [Value] AS [C2] FROM #Orders) AS [T2] ON ([T].[Orderline_ID] = [T2].[Orderline_ID] AND [T2].[Field_ID] = 2)
LEFT JOIN (SELECT [Orderline_ID], [Field_ID], [Value] AS [C3] FROM #Orders) AS [T3] ON ([T].[Orderline_ID] = [T3].[Orderline_ID] AND [T3].[Field_ID] = 3)

Using PIVOT is also a way to achieve this.
SELECT orderline_ID,
[1] AS [total barrels],
[2] AS [vat netto],
[3] AS [vat bruto]
FROM
(
SELECT orderline_ID, Field_ID, [Value]
FROM YourSaleOrderInputDataTable
WHERE Field_ID IN (1, 2, 3) -- optional criteria
) AS src
PIVOT
(
MAX([Value])
FOR Field_ID IN ([1], [2], [3])
) AS pvt
ORDER BY orderline_ID;

Related

SQL to select the 'first' date a project was made inactive for all projects

I am trying to work out the SQL I would need to select certain records, here is an example of what I'm trying to do:
Project number
Active/Inactive
Date
1
A
1/1/20
1
I
3/1/20
1
A
5/1/20
1
I
7/1/20
1
I
9/1/20
2
I
1/1/19
2
A
5/1/19
3
A
1/3/20
3
I
3/3/20
3
I
5/3/20
Note: A=Active project, I=Inactive.
What I would like to do is for each project where the project is currently inactive (i.e. the latest date for the project in the above table is set to I), return the row of the longest time ago it was made inactive, but NOT before it was last active (hope this is understandable!). So for the above table the following would be returned:
Project number
Active/Inactive
Date
1
I
7/1/20
3
I
3/3/20
So proj number 1 is inactive and the earliest time it was made inactive (after the last time it was active) is 7/1/20. Project 2 is not selected as it is currently active. Project 3 is inactive and the earliest time it was made inactive (after the last time it was active) is 3/3/20.
Thanks.
You could use the 'row_number' function to help you.
create TABLE #PROJECT(ProjectNumber int, [Status] varcha(1), [Date] date)
INSERT INTO #PROJECT VALUES
(1 ,'A' ,'1/1/20'),
(1 ,'I' ,'3/1/20'),
(1 ,'A' ,'5/1/20'),
(1 ,'I' ,'7/1/20'),
(1 ,'I' ,'9/1/20'),
(2 ,'I' ,'1/1/19'),
(2 ,'A' ,'5/1/19'),
(3 ,'A' ,'1/3/20'),
(3 ,'I' ,'3/3/20'),
(3 ,'I' ,'5/3/20')
select * from
(SELECT
row_number() over (partition by projectNumber order by [date]) as [index]
,*
FROM
#PROJECT
WHERE
[STATUS] = 'I'
) as a where [index] = 1
Using some effective date joins, this should work. I am using SQL Server. Create your tables and set up the same data set you provided:
CREATE TABLE dbo.PROJECTS
(
PROJ_NUM int NULL,
STTS char(1) NULL,
STTS_DT date NULL
) ON [PRIMARY]
GO
INSERT INTO dbo.PROJECTS values (1, 'A', '1/1/20');
INSERT INTO dbo.PROJECTS values (1, 'I', '3/1/20');
INSERT INTO dbo.PROJECTS values (1, 'A', '5/1/20');
INSERT INTO dbo.PROJECTS values (1, 'I', '7/1/20');
INSERT INTO dbo.PROJECTS values (1, 'I', '9/1/20');
INSERT INTO dbo.PROJECTS values (2, 'I', '1/1/19');
INSERT INTO dbo.PROJECTS values (2, 'A', '5/1/19');
INSERT INTO dbo.PROJECTS values (3, 'A', '1/3/20');
INSERT INTO dbo.PROJECTS values (3, 'I', '3/3/20');
INSERT INTO dbo.PROJECTS values (3, 'I', '5/3/20');
Write a sub-query that filters out just to the projects that are INACTIVE:
-- sub-query that gives you projects that are inactive
SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS CURRSTTS
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = CURRSTTS.PROJ_NUM)
AND CURRSTTS.STTS = 'I'
;
Write another sub-query that provides you the last active status date for each project:
-- sub-query that gives you last active status date for each project
SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS LASTACTV
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = LASTACTV.PROJ_NUM AND ALLP.STTS = 'A')
;
Combine those two sub-queries into a query that gives you the list of inactive projects with their last active status date:
-- sub-query using the 2 above to show only inactive projects with last active stts date
SELECT CURRSTTS.PROJ_NUM, CURRSTTS.STTS, CURRSTTS.STTS_DT, LASTACTV.STTS_DT AS LASTACTV_STTS_DT FROM dbo.PROJECTS CURRSTTS
INNER JOIN
(SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS LASTACTV
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = LASTACTV.PROJ_NUM AND ALLP.STTS = 'A'))
LASTACTV ON CURRSTTS.PROJ_NUM = LASTACTV.PROJ_NUM
WHERE CURRSTTS.STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = CURRSTTS.PROJ_NUM)
AND CURRSTTS.STTS = 'I'
Add one more layer to the query that selects the MIN(STTS_DT) that is greater than the LASTACTV_STTS_DT:
-- final query that uses above sub-query
SELECT P.PROJ_NUM, P.STTS, P.STTS_DT
FROM dbo.PROJECTS P
INNER JOIN (
SELECT CURRSTTS.PROJ_NUM, CURRSTTS.STTS, CURRSTTS.STTS_DT, LASTACTV.STTS_DT AS LASTACTV_STTS_DT FROM dbo.PROJECTS CURRSTTS
INNER JOIN
(SELECT PROJ_NUM, STTS, STTS_DT FROM dbo.PROJECTS LASTACTV
WHERE STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = LASTACTV.PROJ_NUM AND ALLP.STTS = 'A'))
LASTACTV ON CURRSTTS.PROJ_NUM = LASTACTV.PROJ_NUM
WHERE CURRSTTS.STTS_DT = (SELECT MAX(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = CURRSTTS.PROJ_NUM)
AND CURRSTTS.STTS = 'I'
) SUB ON SUB.PROJ_NUM = P.PROJ_NUM
WHERE P.STTS_DT = (SELECT MIN(STTS_DT) FROM dbo.PROJECTS ALLP WHERE ALLP.PROJ_NUM = P.PROJ_NUM AND ALLP.STTS_DT > SUB.LASTACTV_STTS_DT)
The result I get back matches your desired result:
"Greatest n-per group" is the thing to look up when you run accross a problem like this again. Here is a query that will get what you need in postgresSQL.
I realized I changed your column to a boolean, but you will get the gist.
with most_recent_projects as (
select project_number, max(date) date from testtable group by project_number
),
currently_inactive_projects as (
select t.project_number, t.date from testtable t join most_recent_projects mrp on t.project_number = mrp.project_number and t.date = mrp.date where not t.active
),
last_active_date as (
select project_number, date from (
select t.project_number, rank() OVER (
PARTITION BY t.project_number
ORDER BY t.date DESC), t.date
from currently_inactive_projects cip join testtable t on t.project_number = cip.project_number where t.active) t1 where rank = 1
)
-- oldest inactive -- ie, result
select t.project_number, t.active, min(t.date) from last_active_date lad join testtable t on lad.project_number = t.project_number and t.date > lad.date group by t.project_number, t.active;
This is a variation of "gaps and islands" problem.
The query may be like this
SELECT
num,
status,
MIN(date) AS date
FROM (
SELECT
*,
MAX(group_id) OVER (PARTITION BY num) AS max_group_id
FROM (
SELECT
*,
SUM(CASE WHEN status = prev_status THEN 0 ELSE 1 END) OVER (PARTITION BY num ORDER BY date) AS group_id
FROM (
SELECT
*,
LAG(status) OVER (PARTITION BY num ORDER BY date) AS prev_status
FROM projects
) groups
) islands
) q
WHERE status = 'I' AND group_id = max_group_id
GROUP BY num, status
ORDER BY num
Another approach using CTEs
WITH last_status AS (
SELECT
*
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY num ORDER BY date DESC) AS rn
FROM projects
) rns
WHERE rn = 1
),
last_active AS (
SELECT
num,
MAX(date) AS date
FROM projects
WHERE status = 'A'
GROUP BY num
),
last_inactive AS (
SELECT
p.num,
MIN(p.date) AS date
FROM projects p
WHERE p.status = 'I'
AND (
EXISTS (
SELECT 1 FROM last_active la
WHERE la.num = p.num AND la.date < p.date
)
OR NOT EXISTS (
SELECT 1 FROM last_active la
WHERE la.num = p.num
)
)
GROUP BY num
)
SELECT
ls.num,
ls.status,
li.date
FROM last_status ls
JOIN last_inactive li ON li.num = ls.num
WHERE ls.status = 'I'
You can check a working demo with both queries here

How to get the result in below format in SQL Server?

I have a table called recipes with following data.
page_no title
-----------------
1 pancake
2 pizza
3 pasta
5 cookie
page_no 0 is always blank, and missing page_no are blank, I want output as below, for the blank page NULL values in the result.
left_title right_title
------------------------
NULL pancake
Pizza pasta
NULL cookie
I have tried this SQL statement, but it's not returning the desired output:
SELECT
CASE WHEN id % 2 = 0
THEN title
END AS left_title,
CASE WHEN id %2 != 0
THEN title
END AS right_title
FROM
recipes
You are quite close. You just need aggregation:
select max(case when id % 2 = 0 then title end) as left_title,
max(case when id % 2 = 1 then title end) as right_title
from recipes
group by id / 2
order by min(id);
SQL Server does integer division, so id / 2 is always an integer.
Using CTE.. this should be give you a good CTE overview
DECLARE #table TABLE (
pageno int,
title varchar(30)
)
INSERT INTO #table
VALUES (1, 'pancake')
, (2, 'pizza')
, (3, 'pasta')
, (5, 'cookie')
;
WITH cte_pages
AS ( -- generate page numbers
SELECT
0 n,
MAX(pageno) maxpgno
FROM #table
UNION ALL
SELECT
n + 1 n,
maxpgno
FROM cte_pages
WHERE n <= maxpgno),
cte_left
AS ( --- even
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 = 0),
cte_right
AS ( --- odd
SELECT
n,
ROW_NUMBER() OVER (ORDER BY n) rn
FROM cte_pages
WHERE n % 2 <> 0)
SELECT
tl.title left_title,
tr.title right_title --- final output
FROM cte_left l
INNER JOIN cte_right r
ON l.rn = r.rn
LEFT OUTER JOIN #table tl
ON tl.pageno = l.n
LEFT OUTER JOIN #table tr
ON tr.pageno = r.n

Pivot table in SQL-Server 2008

I have a table (SQL Server 2008) with shipments, so "Stage 0" means "New shipment" and consecutive stages are diferent stages of shipment tracking.
I'm trying to pivot the following table:
CREATE TABLE TableName
([ID] int, [IDShip] int, [Date] datetime, [Stage] int, [Arg] int)
;
INSERT INTO TableName
([ID], [IDShip], [Date], [Stage], [Arg])
VALUES
(1, 1, '2013-10-03 08:36:00', 0, Null),
(2, 1, '2013-10-03 08:37:25', 1, 1),
(3, 2, '2013-10-03 08:38:25', 0, Null),
(4, 1, '2013-10-03 08:39:25', 2, 1),
(5, 2, '2013-10-03 08:40:25', 1, 3)
;
("Arg" is ID of Stage0. Select * would be:)
ID IDShip Date Stage Arg
1 1 2013-10-03 08:36:00 0 Null
2 1 2013-10-03 08:37:25 1 1
3 2 2013-10-03 08:38:25 0 Null
4 1 2013-10-03 08:39:25 2 1
5 2 2013-10-03 08:40:25 1 3
into something like:
ID0 IDShip DateShipment ID1 DateFirstStage ID2 DateSecondStage
1 1 2013-10-03 08:36:00 2 2013-10-03 08:37:25 4 2013-10-03 08:39:25
3 2 2013-10-03 08:38:25 5 2013-10-03 08:40:25
Any help? Thanks
Turned out to be a bit more messy than I hoped for but here it is:
SELECT MAX([0]) AS ID0,
IDShip,
(SELECT [Date] FROM TableName T1 WHERE T1.ID = MAX([0]) AND T1.IDShip = Y.IDShip) AS DateShipment,
MAX([1]) AS ID1,
(SELECT [Date] FROM TableName T2 WHERE T2.ID = MAX([1]) AND T2.IDShip = Y.IDShip) AS DateFirstStage,
MAX([2]) AS ID2,
(SELECT [Date] FROM TableName T3 WHERE T3.ID = MAX([2]) AND T3.IDShip = Y.IDShip) AS DateSecondStage
FROM
(SELECT * FROM TableName
PIVOT (MAX([ID]) FOR Stage IN ([0], [1], [2])) AS X) Y
GROUP BY IDShip
You first pivot the table into ID's of 3 stages and then select each stage and its date.
It appears you need to pivot more than one column at the same time. However, standard PIVOT syntax does not support multi-columnar pivoting. You could use it to pivot one of the columns, then, using those as look-up values, pull the other column values with a series of correlated subqueries.
My take on the approach was similar to #Szymon's, except I managed to avoid grouping in the outer query, although I made things more messy at other stages. Here is my attempt:
SELECT
IDShip,
ID0,
ID1,
ID2,
DateShipment = (SELECT Date FROM TableName WHERE ID = p.ID0),
DateFirstStage = (SELECT Date FROM TableName WHERE ID = p.ID1),
DateSecondStage = (SELECT Date FROM TableName WHERE ID = p.ID2)
FROM (
SELECT
ID,
IDShip,
StageID = 'ID' + CAST(Stage AS varchar(10))
FROM TableName
) AS s
PIVOT (
MAX(ID) FOR StageID IN (ID0, ID1, ID2)
) AS p
;
With proper indexing, it may not be too bad, although you could also try this alternative, which uses the older pivoting technique of grouping with conditional aggregation:
SELECT
IDShip,
ID0 = MAX(CASE Stage WHEN 0 THEN ID END),
ID1 = MAX(CASE Stage WHEN 1 THEN ID END),
ID2 = MAX(CASE Stage WHEN 2 THEN ID END),
DateShipment = MAX(CASE Stage WHEN 0 THEN Date END),
DateFirstStage = MAX(CASE Stage WHEN 1 THEN Date END),
DateSecondStage = MAX(CASE Stage WHEN 2 THEN Date END)
FROM TableName
GROUP BY IDShip
;
This SQL Fiddle demo will let you try both solutions.

How to display values of previous rows

I have two tables, I am struggling to write a query that will generate the result I require.
Table 1
CREATE TABLE [Table 1](
[ID] [int] NOT NULL,
[Active_Status] [char](1) NOT NULL,
[Status Change Date] [date] NOT NULL
)
INSERT INTO [Table 1] VALUES (1,'Y','2000-01-15')
INSERT INTO [Table 1] VALUES (1,'N','2003-01-20')
INSERT INTO [Table 1] VALUES (2,'N','2002-01-25')
INSERT INTO [Table 1] VALUES (2,'Y','2003-01-15')
INSERT INTO [Table 1] VALUES (2,'N','2010-01-20')
INSERT INTO [Table 1] VALUES (3,'Y','2005-01-25')
INSERT INTO [Table 1] VALUES (3,'Y','2007-01-20')
INSERT INTO [Table 1] VALUES (3,'N','2011-01-15')
Table 2
CREATE TABLE [Table 2](
[ID] [int] NOT NULL,
[Decision] [varchar](4) NOT NULL,
[Decision Change Date] [date] NOT NULL
)
INSERT INTO [Table 2] VALUES (1,'BUY' ,'2000-05-15')
INSERT INTO [Table 2] VALUES (1,'SELL','2010-05-20')
INSERT INTO [Table 2] VALUES (1,'SELL','2012-05-25')
INSERT INTO [Table 2] VALUES (2,'HOLD','2004-05-15')
INSERT INTO [Table 2] VALUES (2,'BUY' ,'2011-05-10')
INSERT INTO [Table 2] VALUES (3,'SELL','2008-05-15')
INSERT INTO [Table 2] VALUES (3,'BUY' ,'2011-05-25')
My desired output
To start I need to sort my result table by ID and Decision Change Date. Subsequently I need to look up the appropriate Active_Status for the corresponding Decision Change Date.
Likewise I need to display the Active_Status and Decision for the previous period.
Final edit after talking on chat to get a final solution:
DECLARE #Result TABLE
(
TICKR_SYMB VARCHAR (15) NOT NULL
,fromReviewStatus char(10)
,toReviewStatus char(10)
,ReviewStatusChangeDate DATETIME
,fromRestrictionStatus char(10)
,toRestrictionStatus char(10)
,RestrictionStatusChangeDate DATETIME
,fromCoverageStatus char(10)
,toCoverageStatus char(10)
,CoverageStatusChangeDate DATETIME
,fromRating VARCHAR(20)
,toRating VARCHAR(20)
,RatingChangeDate DATETIME
)
/* Rating History */
;WITH DecisionsHistory AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY H.TICKR_SYMB ORDER BY H.[Rating Change Date]) AS Row
,H.TICKR_SYMB
,H.[to Rating] AS toRating
,H.[Rating Change Date]
FROM tblTickerRatingHistory H
)
INSERT #Result
(
TICKR_SYMB
,fromRating
,toRating
,RatingChangeDate
)
SELECT
CurrentHistory.TICKR_SYMB
,LastHistory.toRating AS fromRating
,CurrentHistory.toRating
,CurrentHistory.[Rating Change Date]
FROM DecisionsHistory CurrentHistory
LEFT JOIN DecisionsHistory LastHistory
ON LastHistory.Row = (CurrentHistory.Row - 1)
AND LastHistory.TICKR_SYMB = CurrentHistory.TICKR_SYMB
/* ReviewStatus */
;WITH ReviewStatusHistory AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY R.TICKR_SYMB, R.RatingChangeDate ORDER BY H.ReviewStatusChangeDate DESC) AS Row
,R.TICKR_SYMB
,R.RatingChangeDate
,H.ReviewStatus AS ToReviewStatus
,H.ReviewStatusChangeDate
FROM #Result R
LEFT JOIN tblTickerStatusHistory H
ON H.TICKR_SYMB = R.TICKR_SYMB
AND H.ReviewStatusChangeDate < R.RatingChangeDate
)
UPDATE R
SET
fromReviewStatus = LastActiveHistory.toReviewStatus
,toReviewStatus = CurrentActiveHistory.toReviewStatus
,ReviewStatusChangeDate = CurrentActiveHistory.ReviewStatusChangeDate
FROM #Result R
LEFT JOIN ReviewStatusHistory CurrentActiveHistory
ON CurrentActiveHistory.TICKR_SYMB = R.TICKR_SYMB
AND CurrentActiveHistory.RatingChangeDate = R.RatingChangeDate
AND CurrentActiveHistory.Row = 1
LEFT JOIN ReviewStatusHistory LastActiveHistory
ON LastActiveHistory.TICKR_SYMB = R.TICKR_SYMB
AND LastActiveHistory.RatingChangeDate = R.RatingChangeDate
AND LastActiveHistory.Row = 2
/* CoverageStatus */
;WITH CoverageStatusHistory AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY R.TICKR_SYMB, R.RatingChangeDate ORDER BY H.CoverageStatusChangeDate DESC) AS Row
,R.TICKR_SYMB
,R.RatingChangeDate
,H.CoverageStatus AS ToCoverageStatus
,H.CoverageStatusChangeDate
FROM #Result R
LEFT JOIN tblTickerStatusHistory H
ON H.TICKR_SYMB = R.TICKR_SYMB
AND H.CoverageStatusChangeDate < R.RatingChangeDate
)
UPDATE R
SET
fromCoverageStatus = LastActiveHistory.toCoverageStatus
,toCoverageStatus = CurrentActiveHistory.toCoverageStatus
,CoverageStatusChangeDate = CurrentActiveHistory.CoverageStatusChangeDate
FROM #Result R
LEFT JOIN CoverageStatusHistory CurrentActiveHistory
ON CurrentActiveHistory.TICKR_SYMB = R.TICKR_SYMB
AND CurrentActiveHistory.RatingChangeDate = R.RatingChangeDate
AND CurrentActiveHistory.Row = 1
LEFT JOIN CoverageStatusHistory LastActiveHistory
ON LastActiveHistory.TICKR_SYMB = R.TICKR_SYMB
AND LastActiveHistory.RatingChangeDate = R.RatingChangeDate
AND LastActiveHistory.Row = 2
/*RestrictionStatus */
;WITH RestrictionStatusHistory AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY R.TICKR_SYMB, R.RatingChangeDate ORDER BY H.RestrictionStatusChangeDate DESC) AS Row
,R.TICKR_SYMB
,R.RatingChangeDate
,H.RestrictionStatus AS ToRestrictionStatus
,H.RestrictionStatusChangeDate
FROM #Result R
LEFT JOIN tblTickerStatusHistory H
ON H.TICKR_SYMB = R.TICKR_SYMB
AND H.RestrictionStatusChangeDate < R.RatingChangeDate
)
UPDATE R
SET
fromRestrictionStatus = LastActiveHistory.toRestrictionStatus
,toRestrictionStatus = CurrentActiveHistory.toRestrictionStatus
,RestrictionStatusChangeDate = CurrentActiveHistory.RestrictionStatusChangeDate
FROM #Result R
LEFT JOIN RestrictionStatusHistory CurrentActiveHistory
ON CurrentActiveHistory.TICKR_SYMB = R.TICKR_SYMB
AND CurrentActiveHistory.RatingChangeDate = R.RatingChangeDate
AND CurrentActiveHistory.Row = 1
LEFT JOIN RestrictionStatusHistory LastActiveHistory
ON LastActiveHistory.TICKR_SYMB = R.TICKR_SYMB
AND LastActiveHistory.RatingChangeDate = R.RatingChangeDate
AND LastActiveHistory.Row = 2
SELECT
R1.TICKR_SYMB
,R1.fromCoverageStatus
,R1.toCoverageStatus
,R1.CoverageStatusChangeDate
,R1.fromReviewStatus
,R1.toReviewStatus
,R1.ReviewStatusChangeDate
,R1.fromRestrictionStatus
,R1.toRestrictionStatus
,R1.RestrictionStatusChangeDate
,R1.fromRating
,R1.toRating
,R1.RatingChangeDate
FROM #Result R1
ORDER BY TICKR_SYMB, RatingChangeDate
here's something that should get you started.
create table t1 (
id int, act char(1), scd date
)
create table t2 (
id int, decs varchar(4), dcd date
)
insert into t1 values
( 1, 'y', '20000115'),
( 1, 'n', '20030120'),
( 2, 'n', '20020125'),
( 2, 'y', '20030115'),
( 2, 'n', '20100120'),
( 3,'y','20050125'),
( 3,'y','20070120'),
( 3,'n','20110115')
insert into t2 values
(1,'buy','20000515' ),
(1,'sell', '20100520' ),
(1,'sell', '20120525' ),
(2,'hold', '20040515'),
(2,'buy', '20110510' ),
(3,'sell', '20080515'),
(3,'buy','20110525' )
with decisions as (
select row_number() over (partition by id order by dcd) as rn,
id, decs, dcd from t2
),
activities as
(
select row_number() over (partition by id order by scd) as rn,
id, act, scd from t1
)
select dec_to.id, x.from_act, x.to_act, x.scd, x.from_act, x.to_act, x.scd as scd, dec_from.decs as from_dec, dec_to.decs as to_dec, dec_to.dcd from decisions dec_from
right outer join decisions dec_to on dec_from.id = dec_to.id and
dec_to.rn = dec_from.rn + 1
outer apply (
select top 1 act_to.id, act_from.act as from_act, act_to.act as to_act, act_to.scd
from activities act_to
left outer join activities as act_from
on act_from.id = act_to.id and act_from.rn = act_to.rn - 1
where act_to.id = dec_to.id and act_to.scd <= dec_to.dcd
order by act_to.scd desc
) x
order by dec_to.id, dec_to.rn
yeah it is ugly. probably won't perform well on large datasets without proper indexing. however your requirements are vague, and the rules you use on what rows goes where does not make a whole lot of sense.
The assumption here is that you want the "latest" activity change row thats on or before the decision date. this works nicely and produces the following
ID FROM_ACT TO_ACT SCD FROM_DEC TO_DEC DCD
1 y 2000-01-15 buy 2000-05-15
1 y n 2003-01-20 buy sell 2010-05-20
1 y n 2003-01-20 sell sell 2012-05-25
2 n y 2003-01-15 hold 2004-05-15
2 y n 2010-01-20 hold buy 2011-05-10
3 y y 2007-01-20 sell 2008-05-15
3 y n 2011-01-15 sell buy 2011-05-25
You can play with it at SQLFiddle
You can do this by using CROSS APPLY and LAG.
SELECT t2.ID,
LAG(t1.Active_Status, 1, NULL) OVER (PARTITION BY t2.ID ORDER BY t2.[Decision Change Date]) AS [From Active Status],
t1.Active_Status AS [To Active Status], t1.[Status Change Date] AS [Active Status Change Date],
LAG(t2.Decision, 1, NULL) OVER (PARTITION BY t2.ID ORDER BY t2.[Decision Change Date]) AS [From Decision Status],
t2.Decision AS [To Decision Status], t2.[Decision Change Date]
FROM [Table 2] t2
CROSS APPLY (SELECT TOP 1 *
FROM [Table 1]
WHERE ID = t2.ID AND [Status Change Date] < t2.[Decision Change Date]
ORDER BY [Status Change Date] DESC) t1
ORDER BY t2.ID, [Decision Change Date]

SQL sub query with complex criteria

I have a table like this:
TransId. LayerNo. AccountId.
100. 1. 2.
100. 2. 3.
120. 1. 5.
120. 2. 6.
120. 3. 12.
70. 1. 2.
I want to find transId(s) where:
(LayerNo = 1 and (accountId = 2 or 5))
and
(LayerNo = 2 and (accountId = 3 or 6))
And result set would be row no 1,2,3,4.
How could I write query to get the result?
My database is SQL server 2008 r2
Thanks in advance
Nima
SELECT TransId
FROM your_table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
INTERSECT
SELECT TransId
FROM your_table
WHERE ( layerno = 2
AND accountid IN ( 3, 6 ) )
One approach is to ensure that each transID must have two records that satisfy the conditions you outlined.
SELECT * FROM
TABLE
WHERE TransID IN(
SELECT TransId
FROM table
WHERE ( layerno = 1
AND accountid IN ( 2, 5 ) )
OR ( layerno = 2
AND accountid IN( 3, 6 ) )
GROUP BY
TransId
HAVING Count(*) = 2
)
However this could be a problem if you can have multple records where layerno = 1. So you can use self joins instead to ensure the criteria.
SELECT DISTINCT a.transid
FROM table a
INNER JOIN table b
ON a.transid = b.transid
INNER JOIN table c
ON a.transid = c.transid
WHERE b.layerno = 1
AND accountid IN ( 2, 5 )
AND c.layerno = 2
AND accountid IN ( 3, 6 )
That said Martin's INTERSECT approach is probably the best
Do you mean:
SELECT
TransId,
LayerNo,
AccountId
FROM Table
WHERE (LayerNo = 1 AND AccountId IN (2, 5)) OR
(LayerNo = 2 AND AccountId IN (3, 7))
create table #temp
( rowId Int Identity(1,1), transId int)
INSERT INTO #temp(transId)
select TransId
from TableName
where (layerNo = 1 and accountID IN (2, 5))
OR (layerNo = 2 and accountId IN (3, 6))
select * from #temp
SELECT
base.TransId,
base.LayerNo,
base.AccountId
FROM TableX AS base
JOIN TableX AS a
ON a.TransId = base.TransId
AND a.LayerNo = 1 AND a.AccountId IN (2, 5)
JOIN TableX AS b
ON b.TransId = base.TransId
AND b.LayerNo = 2 AND b.AccountId IN (3, 7)
WHERE (base.LayerNo = 1 AND base.AccountId IN (2, 5))
OR (base.LayerNo = 2 AND base.AccountId IN (3, 7))
This intersection is empty. If you take the values where LayerNo = 1 and LayerNo = 2 and intersect them their intersection is empty because these events are mutually exclusive. I believe this error comes from how the question was originally stated. I might be wrong but the predicate should have been
(LayerNo = 1 and (accountId = 2 or 5)) OR (LayerNo = 2 and (accountId = 3 or 6))
Replace the AND with an OR. If the predicate was stated correctly then the intersect is correct but will always be empty.
SELECT *
FROM table
WHERE (LayerNo = 1 AND (AccountID = 2 OR AccountID = 5))
OR (LayerNo = 2 AND (AccountID = 3 OR AccountID = 6))