SUBSTRING expression works in FROM clause but fails in WHERE clause - sql-server

In SQL Server 2019, I've got this SQL query that works. The second column returned is a hack to get the associated user name that isn't stored elsewhere in this system log. All rows with the Web_Unauthorized event type are of the form:
Unauthorized attempt from account [user name] to access [url requested]
SELECT
SystemLog.Id AS SystemLogId,
SUBSTRING(SystemLog.Message,
LEN('Unauthorized attempt from account') + 2,
PATINDEX('%to access%', SystemLog.Message) - LEN('Unauthorized attempt from account') - 3
) AS UserName,
SystemLog.Timestamp AS ServerTime,
SystemLog.Message
FROM
SystemLog
WHERE
EventType = 'Web_Unauthorized'
But I want to exclude rows where the user name is FOO. So I tried the following, which is identical except for the addition of a second test to the WHERE clause.
SELECT
SystemLog.Id AS SystemLogId,
SUBSTRING(SystemLog.Message,
LEN('Unauthorized attempt from account') + 2,
PATINDEX('%to access%', SystemLog.Message) - LEN('Unauthorized attempt from account') - 3
) AS UserName,
SystemLog.Timestamp AS ServerTime,
SystemLog.Message
FROM
SystemLog
WHERE
EventType = 'Web_Unauthorized'
AND SUBSTRING(SystemLog.Message,
LEN('Unauthorized attempt from account') + 2,
PATINDEX('%to access%', SystemLog.Message) - LEN('Unauthorized attempt from account') - 3
) <> 'FOO'
When I execute this in SSMS, four rows are displayed before SSMS flips me to the Messages pane where it displays the error message:
Invalid length parameter passed to the LEFT or SUBSTRING function.
But the SUBSTRING call I'm making in the WHERE clause is the same one the worked in the FROM clause. Why is it failing in the WHERE clause?
I tried this as well, but with the same result:
SELECT * FROM (
SELECT
SystemLog.Id AS SystemLogId,
SUBSTRING(SystemLog.Message,
LEN('Unauthorized attempt from account') + 2,
PATINDEX('%to access%', SystemLog.Message) - LEN('Unauthorized attempt from account') - 3
) AS UserName,
SystemLog.Timestamp AS ServerTime,
SystemLog.Message
FROM
SystemLog
WHERE
EventType = 'Web_Unauthorized'
) Main
WHERE Main.UserName <> 'FOO'

There's no guarantee that the SUBSTRING expression only evaluates rows where EventType is Web_Unauthorized.
Your extra check doesn't require the big pattern matching, so you could write this instead:
SELECT SystemLog.Id AS SystemLogId,
SUBSTRING(SystemLog.Message,
LEN('Unauthorized attempt from account') + 2,
PATINDEX('%to access%', SystemLog.Message) -
LEN('Unauthorized attempt from account') - 3
) AS UserName,
SystemLog.Timestamp AS ServerTime,
SystemLog.Message
FROM
SystemLog
WHERE
EventType = 'Web_Unauthorized'
AND SystemLog.Message NOT LIKE 'Unauthorized attempt from account _FOO_ to access%'

Related

Join most recent Date AND other fields that belong to that date from another table

I want to JOIN a different table that has DATE values in it, and I only want the most recent Date to be added and te most recent Value that corresponds with that Date.
I have a table in which certain RENTALOBJECTS in the RENTALOBJECTTABLE have a N:1 relationship with the OBJECTTABLE
RENTALOBJECTTABLE:
RENTALOBJECTID, OBJECTID
1, 1
2, 1
3, 2
4, 3
5, 4
6, 4
OBJECTTABLE:
OBJECTID
1
2
3
4
Every OBJECTID can (and usually has, more than 1) VALUE
VALUETABLE:
OBJECTID, VALUE, VALIDFROM, VALIDTO, CODE
1, 2000, 1-1-1950, 31-12-1980, A
1, 3000, 1-1-1981, 31-12-2010, A
1, 4000, 1-1-2013, NULL, A
2, 1000, 1-1-1970, NULL, A
3, 2000, 1-1-2010, NULL, A
4, 2000, 1-1-2000, 31-12-2009, A
4, 3100, 1-1-2010, NULL, B
4, 3000, 1-1-2010, NULL, A
And combined I want for every RentalObject the most recent VALUE to be shown. End result expected:
RENTALOBJECTTABLE_WITHVALUE:
RENTALOBJECTID, OBJECTID, VALUE, VALIDFROM, VALIDTO, CODE
1, 1, 4000, 1-1-2013, NULL, A
2, 1, 4000, 1-1-2013, NULL, A
3, 2, 1000, 1-1-1970, NULL, A
4, 3, 2000, 1-1-2010, NULL, A
5, 4, 3000, 1-1-2010, NULL, A
6, 4, 3000, 1-1-2010, NULL, A
I so far managed to get the Most Recent Date joined to the table with the code below. However, as soon as I want to INCLUDE VALUETABLE.VALUE then the rowcount goes from 5000 (what the original dataset has) to 48000.
SELECT
RENTALOBJECTTABLE.RENTALOBJECTID
FROM RENTALOBJECTTABLE
LEFT JOIN OBJECTTABLE
ON OBJECTTABLE.OBJECTID = RENTALOBJECTTABLE.OBJECTID
LEFT JOIN (
SELECT
OBJECTID,
CODE,
VALUE, --without this one it gives the same rows as the original table
MAX(VALIDFROM) VALIDFROM
FROM VALUETABLE
LEFT JOIN PMETYPE
ON VALUETABLE.CODE = PMETYPE.RECID
AND PMETYPE.REGISTERTYPENO = 6
WHERE PMETYPE.[NAME] = 'WOZ'
GROUP BY OBJECTID, CODE, VALUE
) VALUETABLE ON OBJECTTABLE.OBJECTID = VALUETABLE.OBJECTID
When I include MAX(VALUE) next to the MAX(Date) it obviously has the original 5000 dataset rows again, but now it only selects the most recent date + highest value, which is not always correct.
Anyone any clue about how to solve this issue?
I think I miss something very obvious.
This gets you close
WITH cte AS
(
SELECT
o.OBJECTID,
v.VALUE,
v.VALIDFROM,
v.VALIDTO,
v.CODE,
ROW_NUMBER() OVER (PARTITION BY o.OBJECTID ORDER BY v.VALIDFROM DESC ) rn
FROM dbo.OBJECTTABLE o
INNER JOIN dbo.VALUETABLE v ON v.OBJECTID = o.OBJECTID
)
SELECT ro.RENTALOBJECTID,
ro.OBJECTID,
cte.OBJECTID,
cte.VALUE,
cte.VALIDFROM,
cte.VALIDTO,
cte.CODE
FROM dbo.RENTALOBJECTTABLE ro
INNER JOIN cte ON cte.OBJECTID = ro.OBJECTID
AND rn=1;
However, this might pull out the 3100 value for object 4 - there is nothing to separate the two values with the same validfrom. If you have (or can add) an identity column to the value table, you can use this in the order by on the partitioning to select the row you want.
Your Sample Data
select * into #RENTALOBJECTTABLE from (
SELECT 1 AS RENTALOBJECTID, 1 OBJECTID
UNION ALL SELECT 2,1
UNION ALL SELECT 3,2
UNION ALL SELECT 4,3
UNION ALL SELECT 5,4
UNION ALL SELECT 6,4) A
SELECT * INTO #OBJECTTABLE FROM(
SELECT
1 OBJECTID
UNION ALL SELECT 2
UNION ALL SELECT 3
UNION ALL SELECT 4)AS B
SELECT * INTO #VALUETABLE FROM (
SELECT 1OBJECTID,2000 VALUE,'1-1-1950'VALIDFROM,'31-12-1980' VALIDTO, 'A' CODE
UNION ALL SELECT 1,3000,'1-1-1981','31-12-2010', 'A'
UNION ALL SELECT 1,4000,'1-1-2013',NULL, 'A'
UNION ALL SELECT 2,1000,'1-1-1970',NULL, 'A'
UNION ALL SELECT 3,2000,'1-1-2010',NULL, 'A'
UNION ALL SELECT 4,2000,'1-1-2000','31-12-2009', 'A'
UNION ALL SELECT 4,3100,'1-1-2010',NULL, 'B'
UNION ALL SELECT 4,3000,'1-1-2010',NULL, 'A'
) AS C
Query:
;WITH CTE AS (
SELECT * , ROW_NUMBER()OVER(PARTITION BY OBJECTID ORDER BY OBJECTID DESC)RN FROM #VALUETABLE
)
SELECT RO.RENTALOBJECTID,RO.OBJECTID,C.VALUE,C.VALIDFROM,C.VALIDTO,C.CODE
FROM CTE C
CROSS APPLY (SELECT OBJECTID,MAX(RN)RN FROM CTE C1 WHERE C.OBJECTID=C1.OBJECTID GROUP BY OBJECTID )AS B
INNER JOIN #RENTALOBJECTTABLE RO ON RO.OBJECTID=C.OBJECTID
WHERE C.OBJECTID=B.OBJECTID AND C.RN=B.RN
OutPut Data:
RENTALOBJECTID, OBJECTID, VALUE, VALIDFROM, VALIDTO, CODE
1, 1, 4000, 1-1-2013, NULL, A
2, 1, 4000, 1-1-2013, NULL, A
3, 2, 1000, 1-1-1970, NULL, A
4, 3, 2000, 1-1-2010, NULL, A
5, 4, 3000, 1-1-2010, NULL, A
6, 4, 3000, 1-1-2010, NULL, A

Generate XML in SQL Server

I have a table like this -
Version itemid sampleid
--------------------------------
1 3 23
1 3 24
1 4 45
2 5 24
2 5 23
Where for each version there can be multiple itemid, and for each itemid there can be multiple sampleid.
I want to generate XML for this table in the following manner
<UserVersioningHistory>
<History>
<Version>1</Version>
<itemid>3</itemid>
<sampleid>23,24</sampleid>
</History>
<History>
<Version>1</Version>
<itemid>4</itemid>
<sampleid>45</sampleid>
</History>
<History>
<Version>2</Version>
<BusinessId>5</BusinessId>
<sampleid>24,23</sampleid>
</History>
</UserVersioningHistory>
Each node here can have only one version and itemid but can have multiple sampleid for corresponding itemid and Version Pair.
As I am not familiar in with generating XML in SQL Server, can someone give me a hint to what approach I should use?
Can I accomplish this task using while loop, or I should do this writing a subquery?
Try it out:
select * from
(SELECT
version, itemid,
STUFF(
(SELECT ',' + sampleid
FROM test
WHERE version = a.version AND itemid = a.itemid
FOR XML PATH (''))
, 1, 1, '') AS sampleid
FROM test AS a
GROUP BY version, itemid) as History
for xml auto, root ('UserVersioningHistory')
Always avoid WHILE loops unless when truly necessary.
Preferably you would have multiple tags for the SampleId section if more than one exists.
But to give you the result that you want the following would work. (I created a temp table to imitate your situation.)
I used FOR XML to do the XML formatting and had to use a second FOR XML to concatenate the SampleId sepparated by a comma.
STUFF is only used to remove the first comma in the string.
SET XACT_ABORT ON;
BEGIN TRANSACTION;
SELECT
X.Version,
X.ItemId,
X.SampleId
INTO
#Temp
FROM
(VALUES
(1, 3, 23),
(1, 3, 24),
(1, 4, 45),
(2, 5, 24),
(2, 5, 23)
) X ([Version], ItemId, SampleId)
SELECT
T.Version,
T.ItemId,
STUFF((
SELECT
',' + CONVERT(varchar(MAX), T2.SampleId)
FROM
#Temp T2
WHERE
T2.Version = T.Version AND
T2.ItemId = T.ItemId
FOR XML PATH ('')
),
1,
1,
''
) AS [SampleId]
FROM
#Temp T
GROUP BY
T.Version,
T.ItemId
FOR XML RAW ('History'), ROOT ('UserVersioningHistory'), ELEMENTS
ROLLBACK TRANSACTION;

Blanks rows exported from SQL to excel

When I export my query to excel .CSV I get a blank line after a filled line.
How can I delete the blank line in my query so that it is not exported to excel
exmaple of outcome
SELECT
ID,
CASE
WHEN PATINDEX('%[0-9]%', q.outcome) <> 0
THEN SUBSTRING(q.outcome, 1, PATINDEX('%[0-9]%', q.outcome ) - 1)
ELSE q.outcome
END outcome
FROM (
select
ID,
substring (Eventlog, CHARINDEX('to - usr', EventLog) + 16, LEN(Eventlog)) AS outcome
from Summary
)q
The problem occurs because there's a newline at the end of the outcome.
So you could remove all the Carriage Returns and Line Feed characters from it
...
ID,
Replace(Replace(substring(Eventlog, CHARINDEX('to - usr', EventLog) + 16, LEN(Eventlog)),CHAR(10),''),CHAR(13),'') AS outcome
...
You have not shared the output of your query so not sure what data you are getting. Please try below sql:
SELECT ID,outcome FROM (
SELECT
ID,
CASE
WHEN PATINDEX('%[0-9]%', q.outcome) <> 0
THEN SUBSTRING(q.outcome, 1, PATINDEX('%[0-9]%', q.incoming ) - 1)
ELSE q.outcome
END outcome,
ROW_NUMBER OVER() AS SNO
FROM (
select
ID,
substring (Eventlog, CHARINDEX('to - usr', EventLog) + 16, LEN(Eventlog)) AS outcome
from Summary
)q
) A
WHERE mod(A.SNO,2) = 1

INSERT with Multiple select SQL

I'm using SQL Server 2008. I'm trying to insert data into dv_user_akun table with value that I've selected from another table. Please take a look.
INSERT INTO dv_user_akun (user_id, nik, username, password, kode_tipe, flag)
SELECT
(SELECT
CASE
WHEN right(max(user_id), 8) IS NULL
THEN 'USR00000001'
ELSE ('USR-' + RIGHT('0000000' + cast(right(max(user_id), 7) + 1 as nvarchar), 7))
END user_id
FROM
dv_user_akun) as user_id,
(SELECT
Nip, Nip, '81dc9bdb52d04dc20036dbd8313ed055', PositionCode, 1
FROM
Employee
WHERE
Nip NOT IN (SELECT Nik FROM dv_user_akun))
I get this error
The select list for the INSERT statement contains fewer items than the insert list.
The number of SELECT values must match the number of INSERT columns.
You don't have anything in your select statement. I've changed your subqueries to proper joins rather than the old style ansi joins that you have there. The only problem I still have is that you appear to be using the field Nip to insert into both nik and username fields;
INSERT INTO dv_user_akun (user_id, nik, username, password, kode_tipe, flag)
SELECT
a.user_id
,b.Nip
,b.Nip
,b.pass
,b.PositionCode
,b.number
FROM (
SELECT CASE
WHEN right(max(user_id), 8) IS NULL
THEN 'USR00000001'
ELSE ('USR-' + RIGHT('0000000' + cast(right(max(user_id), 7) + 1 AS NVARCHAR), 7))
END user_id
FROM dv_user_akun
) a
CROSS JOIN
(
SELECT
Nip
,'81dc9bdb52d04dc20036dbd8313ed055' pass
,PositionCode
,1 number
FROM Employee
WHERE Nip NOT IN (
SELECT Nik
FROM dv_user_akun
)
) b

What is wrong with this SQL Query in SQL Server Management Studio?

Trying to run this SQL Query and although it says it was executed successfully, it does not return 1 result. Instead, it returns all rows from the database table as it normally does:
SELECT * FROM [Philadelphia].[dbo].[mmgusers_tbl] [WHERE UserName = 'testing' LIMIT 1];
How do I change this to give me only 1 row when executed?
LIMIT 1 is used in mySQL to limit records. In SQL Server, try using the following query:
SELECT TOP 1 * FROM [Philadelphia].[dbo].[mmgusers_tbl] WHERE UserName = 'testing';
Here is what your looking for
SELECT TOP 1 * FROM [Philadelphia].[dbo].[mmgusers_tbl] WHERE UserName = 'testing';
SELECT * FROM [Philadelphia].[dbo].[mmgusers_tbl] WHERE UserName = 'testing'
and only 1 rows matched a UserName of 'testing'
if you just want a single row
SELECT TOP 1 * FROM [Philadelphia].[dbo].[mmgusers_tbl] WHERE UserName = 'testing'
Try this
SELECT Top 1 * FROM [Philadelphia].[dbo].[mmgusers_tbl]
WHERE UserName = 'testing'
(Or)
Select * FROM
(
SELECT *,Row_Number() Over (Order By (Select Null)) AS RN FROM [Philadelphia].[dbo].[mmgusers_tbl] WHERE UserName = 'testing'
) AS T
Where RN = 1

Resources