SQL merge startdate and endate - tsql

I want to merge several start dates and enddates from a log event (so 1 row for one date).
Input table is:
Ord Desc2 Date_Time
--------------------------
0 Down 1/1/2014 ==> Not merged because no Up after
1 Down 1/2/2014 ==> Rows 1 and 2 should be merged
2 Up 1/3/2014
5 Down 1/4/2014 ==> Rows 5 and 12 should be merged
12 Up 1/6/2014
13 Up 1/7/2014 ==> Not merged because no Down before
So results I'm looking for would be:
Ord DesactivationDateTime ActivationDateTime
----------------------------------------------------
0 1/1/2014
1 1/2/2014 1/3/2014
5 1/4/2014 1/6/2014
13 1/7/2014
SQL Request I've found was this one but it only works for UP/Down couple:
SELECT EventsDesactivation.Ord
, EventsDesactivation.Desc2
, EventsDesactivation.Date_Time AS DesactivationDateTime
, MIN(EventsActivation.Date_Time) AS ActivationDateTime
FROM Journal AS EventsDesactivation
LEFT OUTER JOIN Journal AS EventsActivation
ON EventsActivation.Module=EventsDesactivation.Module --This is one of common rows not displayed in this sample...
WHERE
(EventsDesactivation.Desc2 = 'Down')
AND (EventsActivation.Desc2 = 'Up')
AND (EventsActivation.Ord > EventsDesactivation.Ord)
GROUP BY EventsDesactivation.Ord, EventsDesactivation.Desc2, EventsDesactivation.Date_Time
I'd appreciate if you helped me to find the solution!
My last idea was to do a crazy UNION ALL on couples and singles rows with a final GROUP BY...
Thanks for your help!
Regards,
François

This matches your sample data and expected results but I've got a nagging feeling I'm missing some edge case(s):
declare #t table (Ord int, Desc2 varchar(13),Date_Time date)
insert into #t(Ord,Desc2,Date_Time) values
(0 ,'Down','20140101'),
(1 ,'Down','20140102'),
(2 ,'Up','20140103'),
(5 ,'Down','20140104'),
(12 ,'Up','20140106'),
(13 ,'Up','20140107')
;With numbered as (
select Ord,Desc2,Date_Time,
ROW_NUMBER() OVER (ORDER BY Ord /* or date_time? */) as rn
from
#t
)
select
CASE WHEN n1.Desc2='Up' and n2.Desc2='Up' THEN n2.Ord ELSE n1.Ord END,
CASE WHEN n1.Desc2='Up' and n2.Desc2='Up' THEN NULL ELSE n1.Date_Time END,
CASE WHEN n1.Desc2='Down' and n2.Desc2='Down' THEN NULL ELSE n2.Date_Time END
from
numbered n1
full outer join
numbered n2
on
n1.rn = n2.rn - 1
where
not (n1.Desc2='Up' and n2.Desc2='Down')
Basically, we join each row with the row that logically comes after it. We ignore Up rows which are followed by Down rows. And then we just have to work out if it's Down,Up (normal), Down,Down (report the first row's values only) or Up,Up (report the second row's values only)
Result:
----------- ---------- ----------
0 2014-01-01 NULL
1 2014-01-02 2014-01-03
5 2014-01-04 2014-01-06
13 NULL 2014-01-07

I've applied your idea to my real world and it works perfectly in a stored procedure. Thanks for that!
BUT my SQL client doesn't support stored procedure... Shame on me!
So I decided to create a table function (with pamareters) to call this SQL code.
Unfortunately, I didn't find the way to use you intermediate numbered table (called Event in my use)...
I got following error on line ";WITH Events AS" :
==> select statements included whithin a function cannot return a data to a client.
Here is function code I've done:
CREATE FUNCTION fnSensorsDesactivations
(
#dateStartInString nvarchar(50) = '',
#dateEndInString nvarchar(50) = '',
#Unit nvarchar(50) = null,
#HoursToAddFromGMT int = -2,
#DesactivationEventsFilter nvarchar(50) = 'Validation et Désactivation%',
#BeforeDesactivationComment nvarchar(50) = 'Validation et Désactivation Capteur par ',
#AfterDesactivationComment nvarchar(50) = '. Commentaire : ',
#ActivationEventsFilter nvarchar(50) = 'Activation Capteur par%',
#BeforeActivationUsername nvarchar(50) = 'Activation Capteur par '
)
RETURNS #TableTempToReturn TABLE
(
-- Add the column definitions for the TABLE variable here
--Ord int,
Unit nvarchar(50),
Module nvarchar(255),
Module_Description nvarchar(255),
DésactivationQui nvarchar(255),
DésactivationDateTime nvarchar(255),
DésactivationMessage nvarchar(255),
ActivationQui nvarchar(255),
ActivationDateTime nvarchar(255)
)
AS
BEGIN
DECLARE #ExcludeOrds AS nvarchar(255) -- Must be comma separated and also at first and last character
DECLARE #dateStart AS datetime -- to cast datetime from string because Dream Report is not able to pass parameters with formulas
DECLARE #dateEnd AS datetime -- to cast datetime from string because Dream Report is not able to pass parameters with formulas
SET #ExcludeOrds=',,,,,,,,,'
IF #dateStartInString='' OR #dateEndInString=''
BEGIN
--Init dates from yesterday to today if they are null
SET #dateEnd=GETDATE()
SET #dateStart=DATEADD(year, -1, #DateEnd)
END
ELSE
BEGIN
--Convert datetime from string to datetime format + hours from GMT to follow local datetime settings
SET #dateEnd=DATEADD(hour, #HoursToAddFromGMT, CONVERT(datetime, #dateEndInString, 121))
SET #dateStart=DATEADD(hour, #HoursToAddFromGMT, CONVERT(datetime, #dateStartInString, 121))
END
IF #Unit IS NULL SET #Unit='WIP-BC2'
--Calculate positions/orders of differents Activation and desactivation events
;WITH Events AS -- Here is my SQL error: select statements included whithin a function cannot return a data to a client.
(
SELECT Ord, Unit, Module, Module_Description, Desc2, Date_Time,
ROW_NUMBER() OVER (ORDER BY Unit, Module, Ord) AS rn
FROM
EJournal.dbo.Journal
WHERE
Date_Time <= #dateEnd
AND Unit = #Unit
AND (Desc2 like #DesactivationEventsFilter
OR Desc2 like #ActivationEventsFilter)
--AND (Date_Time like #ActivationEventsFilter and Date_Time<=#dateStart)
AND CHARINDEX(CAST(Ord AS nvarchar), #ExcludeOrds,1)=0
)
--Depending on desactivation and activation orders, select good Events in Activation/descativation columns
SELECT
--n1.Ord,n2.Ord, n1.Desc2, n2.Desc2,
n1.Module
, n1.Module_Description
, CASE
WHEN n1.Desc2 like #ActivationEventsFilter AND n2.Desc2 like #ActivationEventsFilter
THEN ''
WHEN n1.Desc2 like #DesactivationEventsFilter AND (n2.Desc2 like #ActivationEventsFilter OR n2.Ord IS NULL)
THEN ISNULL(LEFT(SUBSTRING(n1.Desc2, LEN(#BeforeDesactivationComment)+2, 255 )
, CHARINDEX(#AfterDesactivationComment, SUBSTRING(n1.Desc2, LEN(#BeforeDesactivationComment)+2, 255)) - 1), '')
WHEN n1.Desc2 like #DesactivationEventsFilter AND (n2.Desc2 like #DesactivationEventsFilter OR n2.Ord IS NULL)
THEN ISNULL(LEFT(SUBSTRING(n1.Desc2, LEN(#BeforeDesactivationComment)+2, 255 ), '')
, CHARINDEX(#AfterDesactivationComment, SUBSTRING(n1.Desc2, LEN(#BeforeDesactivationComment)+2, 255)) - 1)
ELSE ''
END AS DésactivationQui
, CASE
WHEN n1.Desc2 like #ActivationEventsFilter AND n2.Desc2 like #ActivationEventsFilter
THEN ''
WHEN n1.Desc2 like #DesactivationEventsFilter AND (n2.Desc2 like #ActivationEventsFilter OR n2.Ord IS NULL)
THEN ISNULL(CONVERT(nvarchar, n1.Date_Time, 121), '')
WHEN n1.Desc2 like #DesactivationEventsFilter AND (n2.Desc2 like #DesactivationEventsFilter OR n2.Ord IS NULL)
THEN ISNULL(CONVERT(nvarchar, n1.Date_Time, 121), '')
ELSE ''
END AS DésactivationDateTime
, CASE
WHEN n1.Desc2 like #ActivationEventsFilter AND n2.Desc2 like #ActivationEventsFilter
THEN ''
WHEN n1.Desc2 like #DesactivationEventsFilter AND (n2.Desc2 like #ActivationEventsFilter OR n2.Ord IS NULL)
THEN ISNULL(SUBSTRING(n1.Desc2, CHARINDEX(#AfterDesactivationComment, n1.Desc2)+LEN(#AfterDesactivationComment), 255 ) , '')
WHEN n1.Desc2 like #DesactivationEventsFilter AND (n2.Desc2 like #DesactivationEventsFilter OR n2.Ord IS NULL)
THEN ISNULL(SUBSTRING(n1.Desc2, CHARINDEX(#AfterDesactivationComment, n1.Desc2)+LEN(#AfterDesactivationComment), 255 ) , '')
ELSE ''
END AS DésactivationMessage
, CASE
WHEN n1.Desc2 like #ActivationEventsFilter AND n2.Desc2 like #ActivationEventsFilter
THEN ISNULL(SUBSTRING(n2.Desc2, LEN(#BeforeActivationUsername)+2, 255 ),'')
WHEN (n1.Desc2 like #DesactivationEventsFilter OR n1.Ord IS NULL) AND n2.Desc2 like #ActivationEventsFilter
THEN ISNULL(SUBSTRING(n2.Desc2, LEN(#BeforeActivationUsername)+2, 255 ), '')
--WHEN n1.Desc2 like #DesactivationEventsFilter AND n2.Desc2 like #DesactivationEventsFilter THEN ''
-- THEN ''
ELSE ''
END AS ActivationQui
, CASE
WHEN n1.Desc2 like #ActivationEventsFilter AND n2.Desc2 like #ActivationEventsFilter
THEN ISNULL(CONVERT(nvarchar, n2.Date_Time, 121),'')
WHEN (n1.Desc2 like #DesactivationEventsFilter OR n1.Ord IS NULL) AND n2.Desc2 like #ActivationEventsFilter
THEN ISNULL(CONVERT(nvarchar, n2.Date_Time, 121),'')
--WHEN n1.Desc2 like #DesactivationEventsFilter AND n2.Desc2 like #DesactivationEventsFilter
-- THEN ''
ELSE ''
END AS ActivationDateTime
FROM
Events n1
FULL OUTER JOIN
Events n2
ON n1.rn = n2.rn - 1
AND n1.Unit=n2.Unit
AND n1.Module=n2.Module
WHERE NOT (n1.Desc2 like #ActivationEventsFilter AND n2.Desc2 like #DesactivationEventsFilter)
AND n1.Desc2 like #DesactivationEventsFilter
AND n1.Date_Time <= #dateEnd
--AND n2.Date_Time <= #dateEnd
AND (n2.Ord IS NULL OR n2.Date_Time>=#dateStart)
RETURN
END
Would you've a magic solution for it ?
Thanks again for your help!
François

Related

Converting a table with a key and comment field into a key and row for every word in the column field

I have a table with unstructured data I am trying to analyze to try to build a relational lookup. I do not have use of word cloud software.
I really have no idea how to solve this problem. Searching for solutions has lead me to tools that might do this for me that cost money, not coded solutions.
Basically my data looks like this:
CK1 CK2 Comment
--------------------------------------------------------------
1 A This is a comment.
2 A Another comment here.
And this is what I need to create:
CK1 CK2 Words
--------------------------------------------------------------
1 A This
1 A is
1 A a
1 A comment.
2 A Another
2 A comment
2 A here.
What you are trying to do is tokenize a string using a space as a Delimiter. In the SQL world people often refer to functions that do this as a "Splitter". The potential pitfall of using a splitter for this type of thing is how words can be separated by multiple spaces, tabs, CHAR(10)'s, CHAR(13)'s, CHAR()'s, etc. Poor grammar, such as not adding a space after a period results in this:
" End of sentence.Next sentence"
sentence.Next is returned as a word.
The way I like to tokenize human text is to:
Replace any text that isn't a character with a space
Replace duplicate spaces
Trim the string
Split the newly transformed string using a space as the delimiter.
Below is my solution followed by the DDL to create the functions used.
-- Sample Data
DECLARE #yourtable TABLE (CK1 INT, CK2 CHAR(1), Comment VARCHAR(8000));
INSERT #yourtable (CK1, CK2, Comment)
VALUES
(1,'A','This is a typical comment...Follewed by another...'),
(2,'A','This comment has double spaces and tabs and even carriage
returns!');
-- Solution
SELECT t.CK1, t.CK2, split.itemNumber, split.itemIndex, split.itemLength, split.item
FROM #yourtable AS t
CROSS APPLY samd.patReplace(t.Comment,'[^a-zA-Z ]',' ') AS c1
CROSS APPLY samd.removeDupChar8K(c1.newString,' ') AS c2
CROSS APPLY samd.delimitedSplitAB8K(LTRIM(RTRIM(c2.NewString)),' ') AS split;
Results (truncated for brevity):
CK1 CK2 itemNumber itemIndex itemLength item
----------- ---- -------------------- ----------- ----------- --------------
1 A 1 1 4 This
1 A 2 6 2 is
1 A 3 9 1 a
1 A 4 11 7 typical
1 A 5 19 7 comment
...
2 A 1 1 4 This
2 A 2 6 7 comment
2 A 3 14 3 has
2 A 4 18 6 double
...
Note that the splitter I'm using is based of Jeff Moden's Delimited Split8K with a couple tweeks.
Functions used:
CREATE FUNCTION dbo.rangeAB
(
#low bigint,
#high bigint,
#gap bigint,
#row1 bit
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
WITH L1(N) AS
(
SELECT 1
FROM (VALUES
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),(0),
(0),(0)) T(N) -- 90 values
),
L2(N) AS (SELECT 1 FROM L1 a CROSS JOIN L1 b CROSS JOIN L1 c),
iTally AS (SELECT rn = ROW_NUMBER() OVER (ORDER BY (SELECT 1)) FROM L2 a CROSS JOIN L2 b)
SELECT r.RN, r.OP, r.N1, r.N2
FROM
(
SELECT
RN = 0,
OP = (#high-#low)/#gap,
N1 = #low,
N2 = #gap+#low
WHERE #row1 = 0
UNION ALL -- COALESCE required in the TOP statement below for error handling purposes
SELECT TOP (ABS((COALESCE(#high,0)-COALESCE(#low,0))/COALESCE(#gap,0)+COALESCE(#row1,1)))
RN = i.rn,
OP = (#high-#low)/#gap+(2*#row1)-i.rn,
N1 = (i.rn-#row1)*#gap+#low,
N2 = (i.rn-(#row1-1))*#gap+#low
FROM iTally AS i
ORDER BY i.rn
) AS r
WHERE #high&#low&#gap&#row1 IS NOT NULL AND #high >= #low AND #gap > 0;
GO
CREATE FUNCTION samd.NGrams8k
(
#string VARCHAR(8000), -- Input string
#N INT -- requested token size
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT
position = r.RN,
token = SUBSTRING(#string, CHECKSUM(r.RN), #N)
FROM dbo.rangeAB(1, LEN(#string)+1-#N,1,1) AS r
WHERE #N > 0 AND #N <= LEN(#string);
GO
CREATE FUNCTION samd.patReplace8K
(
#string VARCHAR(8000),
#pattern VARCHAR(50),
#replace VARCHAR(20)
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT newString =
(
SELECT CASE WHEN #string = CAST('' AS VARCHAR(8000)) THEN CAST('' AS VARCHAR(8000))
WHEN #pattern+#replace+#string IS NOT NULL THEN
CASE WHEN PATINDEX(#pattern,token COLLATE Latin1_General_BIN)=0
THEN ng.token ELSE #replace END END
FROM samd.NGrams8K(#string, 1) AS ng
ORDER BY ng.position
FOR XML PATH(''),TYPE
).value('text()[1]', 'VARCHAR(8000)');
GO
CREATE FUNCTION samd.delimitedSplitAB8K
(
#string VARCHAR(8000), -- input string
#delimiter CHAR(1) -- delimiter
)
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT
itemNumber = ROW_NUMBER() OVER (ORDER BY d.p),
itemIndex = CHECKSUM(ISNULL(NULLIF(d.p+1, 0),1)),
itemLength = CHECKSUM(item.ln),
item = SUBSTRING(#string, d.p+1, item.ln)
FROM (VALUES (DATALENGTH(#string))) AS l(s) -- length of the string
CROSS APPLY
(
SELECT 0 UNION ALL -- for handling leading delimiters
SELECT ng.position
FROM samd.NGrams8K(#string, 1) AS ng
WHERE token = #delimiter
) AS d(p) -- delimiter.position
CROSS APPLY (VALUES( --LEAD(d.p, 1, l.s+l.d) OVER (ORDER BY d.p) - (d.p+l.d)
ISNULL(NULLIF(CHARINDEX(#delimiter,#string,d.p+1),0)-(d.p+1), l.s-d.p))) AS item(ln);
GO
CREATE FUNCTION dbo.RemoveDupChar8K(#string varchar(8000), #char char(1))
RETURNS TABLE WITH SCHEMABINDING AS RETURN
SELECT NewString =
replace(replace(replace(replace(replace(replace(replace(
#string COLLATE LATIN1_GENERAL_BIN,
replicate(#char,33), #char), --33
replicate(#char,17), #char), --17
replicate(#char,9 ), #char), -- 9
replicate(#char,5 ), #char), -- 5
replicate(#char,3 ), #char), -- 3
replicate(#char,2 ), #char), -- 2
replicate(#char,2 ), #char); -- 2
GO
1) If we are using SQL Server 2016 and above then we should probably
use the built-in function STRING_SPLIT
-- SQL 2016and above
DECLARE #txt NVARCHAR(100) = N'This is a comment.'
select [value] from STRING_SPLIT(#txt, ' ')
2) Only if 1 does not fit, then if the number of separation (the space in our case) is less then 3 which fit your sample data, then we should probably use PARSENAME
-- BEFORE SQL 2016 if we have less than 4 parts
DECLARE #txt NVARCHAR(100) = N'This is a comment.'
DECLARE #Temp NVARCHAR(200) = REPLACE (#txt,'.','#')
SELECT t FROM (VALUES(1),(2),(3),(4))T1(n)
CROSS APPLY (SELECT REPLACE(PARSENAME(REPLACE(#Temp,' ','.'),T1.n), '#','.'))T2(t)
3) Only if the 1 and 2 does not fit, then we should use SQLCLR function
http://dataeducation.com/sqlclr-string-splitting-part-2-even-faster-even-more-scalable/
4) Only if we cannot use 1,2 and we cannot use SQLCLR (which implies a real problematic administration and has nothing with security since you can have all the SQLCLR function in a read-only database for the use of all users, as I explain in my lectures), then you can use T-SQL and create UDF.
https://sqlperformance.com/2012/07/t-sql-queries/split-strings

TSQL - Parse address function, unable to return result

Function px_explode will be provided with two parameters:
separator
string
Final result will look like this:
SELECT * FROM dbo.px_explode('xxy', 'alfaxxybetaxxygama')
and will return
But...
Query won't finish execution, so I assume that I ran into an infinite loop here, now assuming this, my question might be.
How can I avoid the infinite loop I ran into and what am I missing?
Code:
CREATE FUNCTION dbo.px_explode
(#separator VARCHAR(10), #string VARCHAR(2000))
RETURNS #expl_tbl TABLE
(val VARCHAR(100))
AS
BEGIN
IF (CHARINDEX(#separator, #string) = 0) and (LTRIM(RTRIM(#string)) <> '')
INSERT INTO #expl_tbl VALUES(LTRIM(RTRIM(#string)))
ELSE
BEGIN
WHILE CHARINDEX(#separator, #string) > 0
BEGIN
IF (LTRIM(RTRIM(LEFT(#string, CHARINDEX(#separator, #string) - 1)))
<> '')
INSERT INTO #expl_tbl VALUES(LTRIM(RTRIM(LEFT(#string,
CHARINDEX(#separator, #string) - 1))))
END
IF LTRIM(RTRIM(#string)) <> ''
INSERT INTO #expl_tbl VALUES(LTRIM(RTRIM(#string)))
END
RETURN
END
Loops are bad and so are mutli-statement table valued functions (e.g. where you define the table). If performance is important then you want a tally table and and inline table valued function (iTVF).
For a high-performing way to resolve this I would first grab a copy of Ngrams8k. The solution you're looking for will look like this:
DECLARE #string varchar(8000) = 'alfaxxybetaxxygama',
#delimiter varchar(20) = 'xxy'; -- use
SELECT
itemNumber = row_number() over (ORDER BY d.p),
itemIndex = isnull(nullif(d.p+l.d, 0),1),
item = SUBSTRING
(
#string,
d.p+l.d, -- delimiter position + delimiter length
isnull(nullif(charindex(#delimiter, #string, d.p+l.d),0) - (d.p+l.d), 8000)
)
FROM (values (len(#string), len(#delimiter))) l(s,d) -- 1 is fine for l.d but keeping uniform
CROSS APPLY
(
SELECT -(l.d) union all
SELECT ng.position
FROM dbo.NGrams8K(#string, l.d) as ng
WHERE token = #delimiter
) as d(p); -- delimiter.position
Which returns
itemNumber itemIndex item
-------------------- -------------------- ---------
1 1 alfa
2 8 beta
3 15 gama
Against a table it would look like this:
DECLARE #table table (string varchar(8000));
INSERT #table VALUES ('abcxxyXYZxxy123'), ('alfaxxybetaxxygama');
DECLARE #delimiter varchar(100) = 'xxy';
SELECT *
FROM #table t
CROSS APPLY
(
SELECT
itemNumber = row_number() over (ORDER BY d.p),
itemIndex = isnull(nullif(d.p+l.d, 0),1),
item = SUBSTRING
(
t.string,
d.p+l.d, -- delimiter position + delimiter length
isnull(nullif(charindex(#delimiter, t.string, d.p+l.d),0) - (d.p+l.d), 8000)
)
FROM (values (len(t.string), len(#delimiter))) l(s,d) -- 1 is fine for l.d but keeping uniform
CROSS APPLY
(
SELECT -(l.d) union all
SELECT ng.position
FROM dbo.NGrams8K(t.string, l.d) as ng
WHERE token = #delimiter
) as d(p) -- delimiter.position
) split;
Results:
string itemNumber itemIndex item
------------------------- -------------------- -------------------- ------------------
abcxxyXYZxxy123 1 1 abc
abcxxyXYZxxy123 2 7 XYZ
abcxxyXYZxxy123 3 13 123
alfaxxybetaxxygama 1 1 alfa
alfaxxybetaxxygama 2 8 beta
alfaxxybetaxxygama 3 15 gama
My favourite is the XML splitter. This needs no function and is fully inlineable. If you can introduce a function to your database, the suggested links in Gareth's comment give you some very good ideas.
This is simple and quite straight forward:
DECLARE #YourString VARCHAR(100)='alfaxxybetaxxygama';
SELECT nd.value('text()[1]','nvarchar(max)')
FROM (SELECT CAST('<x>' + REPLACE((SELECT #YourString AS [*] FOR XML PATH('')),'xxy','</x><x>') + '</x>' AS XML)) AS A(Casted)
CROSS APPLY A.Casted.nodes('/x') AS B(nd);
This will first transform your string to an XML like this
<x>alfa</x>
<x>beta</x>
<x>gama</x>
... simply by replacing the delimiters xxy with XML tags. The rest is easy reading from XML .nodes()

SQL Select where exclusive Or /Case

I need a query based on an exclusive Or statement for this I try using the case but I won't get a result in case of Null...
...
and b.[U_Periode] = CASE
when (b.U_Periode= #period) then #period
when (b.U_Periode is NULL ) then null
end
...
The Case that won't be catched is... if B.U_Status is null and b.U_Periode is null.
If the var Periode match the Value and the U_Status = 0 or 1
the only way getting this working for me was this:
...
and
ISNULL( b.[U_Status],'0') = CASE
when (b.U_Status= '1') then '1'
when (isnull( b.U_Status,'0')= '0') then '0'
end
and
ISNULL (b.[U_Periode],'01.01.1901') = CASE
when (b.U_Periode= #period) then #period
when (ISNULL (b.U_Periode,'01.01.1901') = '01.01.1901' ) then '01.01.1901'
end
are there any other better solutions for this?
Best regards
Oliver
Okay... here is my Problem
Table1
InsID ContractID
1 1
2 1
Table2
ID insid Period Status Count
1 1 null null 100
2 1 30.09.2015 1 500
3 2 null null 100
4 2 30.09.2015 1 500
Case '31.08.2015'
in total Value should be 200
in case of '30.09.2015'
the Value should be 1.000
XOR /OR will do the same in this case.
Value case '31.08.2015' = 200
value Case ' 30.09.2015 = 2200
So this is somesing like a subquery
left join (
[dbo].[Table2]b
inner join [dbo].[Table 3]K on k.DocEntry = b.DocEntry and CAST( k.U_CSetID as int) >0
)
on b.[U_InsID] in(select... but here I should have an if statement...
If there are results matching the Date join this
If not than join the result NULL is matching to periode...
Okay.. here is the complete query
the Table2 with the Date of 31.08.2015 should have one Record that include
B_STATUS = Null and B.Preiode = null and there is no available record with the U_Periode '31.08.2015' and a Staus ...
with a date of 30.09.2015
there is a Record matching U_Period = '30.09.2015' in this case the Record with U_Period=null should not effect the result...
Declare #period as varchar(20)= '31-08-2015 00:00:00'
declare #Customer as Varchar(15)='12345'
declare #Contract as varchar(30) = '123'
declare #test as varchar(1) = null
select SUM(cast(K.U_Count as decimal))as counter, K.U_CounterTyp
from [dbo].[Table1] a
left join (
[dbo].[Table2]b
inner join [dbo].[Table 3]K on k.DocEntry = b.DocEntry and CAST( k.U_CSetID as int) >0
)
on b.[U_InsID]=a.[insID] and b.[U_ObjectType]in ('5','1') and
ISNULL( b.[U_Status],'0') = CASE
when (b.U_Status= '1') then '1'
when (isnull( b.U_Status,'0')= '0') then '0'
end
and
ISNULL (b.[U_Periode],'01.01.1901') = CASE
when (b.U_Periode= #period) then #period
when (ISNULL (b.U_Periode,'01.01.1901') = '01.01.1901' ) then '01.01.1901'
end
where a.[customer] =#Customer and a.[Status]='A' and a.[U_ContrCount]='1'
and a.[manufSN] in(
select c.[ManufSN] from [dbo].[Table4] c
inner join [dbo].[OCTR]d on d.[ContractID] = c.[ContractID]
where c.[ManufSN]=a.[manufSN]
and d.[CstmrCode] = a.[customer] and d.[ContractID]=#Contract
)
group by K.U_CounterTyp
you must use the "^" operand, this is XOR operand in TSQL
expression ^ expression
the expression must return or 0 or 1...
stupid example:
WHERE (name like "stackoverflow") ^ (age > 10)
font: https://msdn.microsoft.com/en-us/library/ms190277(v=sql.105).aspx
Update for your check
WHERE (CONVERT(VARCHAR(10), a.[U_Periode], 104) = '30.08.2015') != (a.U_Periode IS NULL)
Olay here is my funktion that chek either a Date Result is there or not.
The only thing is, that the performance is not the best if I run hundreths of sentences ... foreach record (up to app. 1000) I need to query each subtable... and there are many many records in...
Anyway this is the function
CREATE FUNCTION fn_GetInsCounters(#InsId as Varchar(30), #Date as datetime)
returns datetime
as
begin
declare #count as int = 0
declare #Retruns as Datetime
set #count =
(
select count( b.Docentry)
from [dbo].[Table2]b
where b.U_Insid =#InsID
and b.U_Status <> '2'
and b.[U_ObjectType]in ('5','1')
and b.U_Periode=#Date
)
if(#count>0)
begin
set #Retruns = #date
end
else
begin
set #Retruns = '01.01.1901'
end
return #Retruns
end
if anyone gets a better idea???
Best regards
Oliver

Split string with a separator and set each part in variables in TSQL

I need to split some strings with this format:
V_9_0_2_2_70_0_0_3_B
The separator is '_'.
I want that each value is stored in my variables
DECLARE #w_grup1 char(1),
#w_grup2 char(1),
#w_grup3 varchar(10),
#w_grup4 char(1),
#w_grup5 char(1),
#w_grup6 varchar(10),
#w_grup7 char(1),
#w_grup8 varchar(10),
#w_grup9 char(1),
#w_grup10 char(1)
How do I do that? Some suggestion?
I figured your best bet is a recursive CTE. Note: I didn't load the data into a table but I figure you can do that easily enough from my results. If you need anything else, let me know.
DECLARE #YourTable table (ID INT IDENTITY(1,1), String varchar(200))
INSERT #YourTable(String)
VALUES ('V_9_0_2_2_70_0_0_3_B'),
('ABC_01_23_45_67_89_10_11_12_XYZ');
WITH SplitString AS
(
SELECT ID,
LEFT(String,CHARINDEX('_',String)-1) AS Part,
RIGHT(String,LEN(String)-CHARINDEX('_',String)) AS Remainder,
1 AS RecursionCount
FROM #YourTable
WHERE String IS NOT NULL AND CHARINDEX('_',String) > 0
UNION ALL
SELECT ID,
LEFT(Remainder,CHARINDEX('_',Remainder)-1),
RIGHT(Remainder,LEN(Remainder)-CHARINDEX('_',Remainder)),
RecursionCount + 1
FROM SplitString
WHERE Remainder IS NOT NULL AND CHARINDEX('_',Remainder) > 0
UNION ALL
SELECT ID,
Remainder,
null,
recursionCount + 1
FROM SplitString
WHERE Remainder IS NOT NULL AND CHARINDEX('_',Remainder) = 0
)
SELECT *
FROM
(
SELECT CONCAT('w_grup',RecursionCount) w_grups,Part,ID
FROM SplitString
) A
PIVOT
(
MAX(Part) FOR w_grups IN (w_grup1,w_grup2,w_grup3,w_grup4,w_grup5,w_grup6,w_grup7,w_grup8,w_grup9,w_grup10)
) pvt
Results:
ID w_grup1 w_grup2 w_grup3 w_grup4 w_grup5 w_grup6 w_grup7 w_grup8 w_grup9 w_grup10
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 V 9 0 2 2 70 0 0 3 B
2 ABC 01 23 45 67 89 10 11 12 XYZ
So, something like this?
with split as
(
select
item = cast('' as varchar(max)),
source = cast('V_9_0_2_2_70_0_0_3_B' as varchar(max))
union all
select
item = substring(source, 1, charindex('_', source)),
source = substring(source, charindex('_', source) + 1, 10000)
from split
where source > ''
)
select substring(item, 1, charindex('_', item) -1)
from split
where item > ''
from this question

Getting the minimum of two values in SQL

I have two variables, one is called PaidThisMonth, and the other is called OwedPast. They are both results of some subqueries in SQL. How can I select the smaller of the two and return it as a value titled PaidForPast?
The MIN function works on columns, not variables.
SQL Server 2012 and 2014 supports IIF(cont,true,false) function. Thus for minimal selection you can use it like
SELECT IIF(first>second, second, first) the_minimal FROM table
While IIF is just a shorthand for writing CASE...WHEN...ELSE, it's easier to write.
The solutions using CASE, IIF, and UDF are adequate, but impractical when extending the problem to the general case using more than 2 comparison values. The generalized
solution in SQL Server 2008+ utilizes a strange application of the VALUES clause:
SELECT
PaidForPast=(SELECT MIN(x) FROM (VALUES (PaidThisMonth),(OwedPast)) AS value(x))
Credit due to this website:
http://sqlblog.com/blogs/jamie_thomson/archive/2012/01/20/use-values-clause-to-get-the-maximum-value-from-some-columns-sql-server-t-sql.aspx
Use Case:
Select Case When #PaidThisMonth < #OwedPast
Then #PaidThisMonth Else #OwedPast End PaidForPast
As Inline table valued UDF
CREATE FUNCTION Minimum
(#Param1 Integer, #Param2 Integer)
Returns Table As
Return(Select Case When #Param1 < #Param2
Then #Param1 Else #Param2 End MinValue)
Usage:
Select MinValue as PaidforPast
From dbo.Minimum(#PaidThisMonth, #OwedPast)
ADDENDUM:
This is probably best for when addressing only two possible values, if there are more than two, consider Craig's answer using Values clause.
For SQL Server 2022+ (or MySQL or PostgreSQL 9.3+), a better way is to use the LEAST and GREATEST functions.
SELECT GREATEST(A.date0, B.date0) AS date0,
LEAST(A.date1, B.date1, B.date2) AS date1
FROM A, B
WHERE B.x = A.x
With:
GREATEST(value [, ...]) : Returns the largest (maximum-valued) argument from values provided
LEAST(value [, ...]) Returns the smallest (minimum-valued) argument from values provided
Documentation links :
MySQL http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html
Postgres https://www.postgresql.org/docs/current/functions-conditional.html
SQL Server https://learn.microsoft.com/en-us/sql/t-sql/functions/logical-functions-least-transact-sql
I just had a situation where I had to find the max of 4 complex selects within an update.
With this approach you can have as many as you like!
You can also replace the numbers with aditional selects
select max(x)
from (
select 1 as 'x' union
select 4 as 'x' union
select 3 as 'x' union
select 2 as 'x'
) a
More complex usage
#answer = select Max(x)
from (
select #NumberA as 'x' union
select #NumberB as 'x' union
select #NumberC as 'x' union
select (
Select Max(score) from TopScores
) as 'x'
) a
I'm sure a UDF has better performance.
Here is a trick if you want to calculate maximum(field, 0):
SELECT (ABS(field) + field)/2 FROM Table
returns 0 if field is negative, else, return field.
Use a CASE statement.
Example B in this page should be close to what you're trying to do:
http://msdn.microsoft.com/en-us/library/ms181765.aspx
Here's the code from the page:
USE AdventureWorks;
GO
SELECT ProductNumber, Name, 'Price Range' =
CASE
WHEN ListPrice = 0 THEN 'Mfg item - not for resale'
WHEN ListPrice < 50 THEN 'Under $50'
WHEN ListPrice >= 50 and ListPrice < 250 THEN 'Under $250'
WHEN ListPrice >= 250 and ListPrice < 1000 THEN 'Under $1000'
ELSE 'Over $1000'
END
FROM Production.Product
ORDER BY ProductNumber ;
GO
This works for up to 5 dates and handles nulls. Just couldn't get it to work as an Inline function.
CREATE FUNCTION dbo.MinDate(#Date1 datetime = Null,
#Date2 datetime = Null,
#Date3 datetime = Null,
#Date4 datetime = Null,
#Date5 datetime = Null)
RETURNS Datetime AS
BEGIN
--USAGE select dbo.MinDate('20120405',null,null,'20110305',null)
DECLARE #Output datetime;
WITH Datelist_CTE(DT)
AS (
SELECT #Date1 AS DT WHERE #Date1 is not NULL UNION
SELECT #Date2 AS DT WHERE #Date2 is not NULL UNION
SELECT #Date3 AS DT WHERE #Date3 is not NULL UNION
SELECT #Date4 AS DT WHERE #Date4 is not NULL UNION
SELECT #Date5 AS DT WHERE #Date5 is not NULL
)
Select #Output=Min(DT) FROM Datelist_CTE;
RETURN #Output;
END;
Building on the brilliant logic / code from mathematix and scottyc, I submit:
DECLARE #a INT, #b INT, #c INT = 0;
WHILE #c < 100
BEGIN
SET #c += 1;
SET #a = ROUND(RAND()*100,0)-50;
SET #b = ROUND(RAND()*100,0)-50;
SELECT #a AS a, #b AS b,
#a - ( ABS(#a-#b) + (#a-#b) ) / 2 AS MINab,
#a + ( ABS(#b-#a) + (#b-#a) ) / 2 AS MAXab,
CASE WHEN (#a <= #b AND #a = #a - ( ABS(#a-#b) + (#a-#b) ) / 2)
OR (#a >= #b AND #a = #a + ( ABS(#b-#a) + (#b-#a) ) / 2)
THEN 'Success' ELSE 'Failure' END AS Status;
END;
Although the jump from scottyc's MIN function to the MAX function should have been obvious to me, it wasn't, so I've solved for it and included it here: SELECT #a + ( ABS(#b-#a) + (#b-#a) ) / 2. The randomly generated numbers, while not proof, should at least convince skeptics that both formulae are correct.
Use a temp table to insert the range of values, then select the min/max of the temp table from within a stored procedure or UDF. This is a basic construct, so feel free to revise as needed.
For example:
CREATE PROCEDURE GetMinSpeed() AS
BEGIN
CREATE TABLE #speed (Driver NVARCHAR(10), SPEED INT);
'
' Insert any number of data you need to sort and pull from
'
INSERT INTO #speed (N'Petty', 165)
INSERT INTO #speed (N'Earnhardt', 172)
INSERT INTO #speed (N'Patrick', 174)
SELECT MIN(SPEED) FROM #speed
DROP TABLE #speed
END
Select MIN(T.V) FROM (Select 1 as V UNION Select 2 as V) T
SELECT (WHEN first > second THEN second ELSE first END) the_minimal FROM table