What is the best way to convert this to SQL - c#

I know for the Database Guru's here this should be a doddle. I have a Field in my database in the format of ' A/B/C/D/E/F '
The format is irrelevant I generally need the last two parts so for the above it would be
'EF'
But if I had another string
AB/CD/EF/GH == EFGH
And I am looking to getting the last two parts to return like this 'EFGH'
Does anyone know an SQL Function I can do that will split this
I am using Microsoft SQL Server 2012 - I Hope this helps,
Here is C# Code.
var myText = "A/B/C/D/E/F";
var identificationArray = myText.Split('/');
if(identificationArray.Length >= 2)
{
var friendlyId = identificationArray[identificationArray.Length - 2] + identificationArray[identificationArray.Length - 1];
return friendlyId;
}
return "";

Here is one answer that searches a string in reverse order for the second forward slash and returns that substring with forward slashes removed:
declare #s varchar(20)
set #s = 'A/B/C/D/E/F'
-- result: 'EF'
select reverse(replace(left(reverse(#s), charindex('/', reverse(#s), charindex('/', reverse(#s)) + 1)), '/', ''))
set #s = 'AB/CD/EF/GH'
-- result: 'EFGH'
select reverse(replace(left(reverse(#s), charindex('/', reverse(#s), charindex('/', reverse(#s)) + 1)), '/', ''))
Testing this with a couple of other inputs:
set #s = '/AB/CD' -- result: 'ABCD'
set #s = 'AB/CD' -- result: an empty string '' -- you may not want this result
set #s = 'AB' -- result: an empty string ''
Here is a ridiculously complicated way to do the same thing with a series of common table expressions (CTEs). Credit goes to Itzik Ben-Gan for the CTE technique to generate a tally table using cross-joins:
declare #s varchar(50)
set #s = 'A/B/C/D/E/F/G'
--set #s = 'AB/CD/EF/GH'
--set #s = 'AB/CD'
--set #s = 'ABCD/EFGH/IJKL'
--set #s = 'A/B'
-- set #s = 'A'
declare #result varchar(50)
set #result = ''
;with
-- cross-join a meaningless set of data together to create a lot of rows
Nbrs_2 (n) AS (SELECT 1 UNION SELECT 0 ),
Nbrs_4 (n) AS (SELECT 1 FROM Nbrs_2 n1 CROSS JOIN Nbrs_2 n2),
Nbrs_16 (n) AS (SELECT 1 FROM Nbrs_4 n1 CROSS JOIN Nbrs_4 n2),
Nbrs_256 (n) AS (SELECT 1 FROM Nbrs_16 n1 CROSS JOIN Nbrs_16 n2),
Nbrs_65536(n) AS (SELECT 1 FROM Nbrs_256 n1 CROSS JOIN Nbrs_256 n2),
Nbrs (n) AS (SELECT 1 FROM Nbrs_65536 n1 CROSS JOIN Nbrs_65536 n2),
-- build a table of numbers from the data above; this is insanely fast
nums(n) as
(
select row_number() over(order by n) from Nbrs
),
-- split the string into separate rows per letter
letters(n, c) as
(
select n, substring(#s, n, 1)
from nums
where n < len(#s) + 1
),
-- count the slashes from the rows in descending order
-- the important slash is the second one from the end
slashes(n, num) as
(
select n, ROW_NUMBER() over (order by n desc)
from letters
where c = '/'
)
select #result = #result + c
from letters
where n > (select n from slashes where num = 2) -- get everything after the second slash
and c <> '/' -- and drop out the other slash
select #result

You need to reverse the string and find the 2nd occurrence of the / character. Once you have that it is pretty straight forward, just a lot of function calls to get the desired format
declare #test varchar(max);
set #test = 'b/b/a/v/d';
select
case
when charindex('/', reverse(#test), charindex('/', reverse(#test))+1) = 0 then ''
else replace(reverse(substring(reverse(#test), 0, charindex('/', reverse(#test), charindex('/', reverse(#test))+1))), '/', '')
end

I understand that you want to do this in SQL. But did you think about using SQL CLR User Defined Functions? It will execute faster than SQL. you anyways have the logic implemented in C# which definitely simpler than the logic in SQL.

Late to the party, but here is my attempt:
declare #text varchar(max), #reversedtext varchar(max)
select #text = 'AB/CD/EF/GH'
select #reversedtext = reverse(#text)
declare #pos1 int
declare #pos2 int
declare #pos3 int
select #pos1 = charindex('/', #reversedtext)
select #pos2 = charindex('/', replace(#reversedtext, left(#reversedtext, #pos1), ''))
select #pos3 = #pos1 + #pos2
select REPLACE(RIGHT(#text, #pos3), '/', '')

Related

SQL Geo-Spatial Linked Server Query

I have one question on the TSQL Linked Server Query. Linked Server is GIS enforced so we pass the coordinates to that server which it returns the data from the Linked Server. Please find the below-working query.
DECLARE #input varchar(max), #sql varchar(max);
SET #input = N'((-119.470830216356 46.2642458295079,-119.470722927989 46.2642050348762,-119.470076515615 46.2647075484513,-119.470240130371 46.2647075484512,-119.470830216356 46.2642458295079))'
BEGIN
SELECT #sql = 'select * from openquery([LinkedServerName],''DECLARE #b geometry;
SET #b = geometry::STGeomFromText(''''POLYGON '+ #input + ' '''', 4326);
SET #b = #b.MakeValid();
SELECT * from [Database].[Table] AS b
where b.Shape.STIntersects(#b.STCentroid()) = 1'')'
END
EXEC(#sql)
But the issue is sometimes we have to pass more than 8000 characters to the input parameter #input since it is varchar(max) and EXEC command both have an 8000 character limitation. So we are trying to get rid of Dynamic SQL so that we can pass the input using 2 input variables (We have implemented splitting the input into subsets each of 8000 characters in our C# code and sending them as 2 different inputs to the SQL Query). We have tried the below query in the Actual Server (Linked Server) which is working fine.
DECLARE #b geometry
SET #input = N'((-119.470830216356 46.2642458295079,-119.470722927989 46.2642050348762,'
SET #input2 = N'-119.470076515615 46.2647075484513,-119.470240130371 46.2647075484512,-119.470830216356 46.2642458295079))'
SELECT #b = geometry::STGeomFromText('POLYGON ' + #input + #input2 + '', 4326)
SELECT #b = #b.MakeValid()
SELECT * FROM [Database].[TableName] AS b
WHERE b.Shape.STIntersects(#b.STCentroid()) = 1
We tried below SQL Linked query in our local server but it is throwing below error
DECLARE #input varchar(max), #input2 varchar(max);
SET #input = N'((-119.470830216356 46.2642458295079,-119.470722927989 46.2642050348762,'
SET #input2 = N'-119.470076515615 46.2647075484513,-119.470240130371 46.2647075484512,-119.470830216356 46.2642458295079))'
SELECT * FROM OPENQUERY([LinkedServerName],
'DECLARE #b geometry;
SELECT #b = geometry::STGeomFromText(''''POLYGON ' + #input + #input2 + '' ', 4326);
SELECT #b = #b.MakeValid();
SELECT * FROM [DatabaseName].[TableName] AS b
where b.Shape.STIntersects(#b.STCentroid()) = 1') AS AD
In the above query, an issue has been highlighted in the attached image.
Help is really appreciated.

Many SQL rows into one

I've got a stored procedure which joins a number of tables to produce a large resultset which is then returned to my application. The application in turn loops through the results and combines rows on a particular ID and chooses data per row to include in a new object. This is perhaps easiest to explain using an example:
Inspection, Desc, Value
1, Description1, 3
1, Description2, 2
1, Description3, 5
This is in code turned into
Inspection, Description1, Description2, Description3
1, 3, 2, 5
The point of this is to have one row per inspection item with item description as headers and value as the cell value for inspection row and header. This is then exported to Excel.
The question is: how do I do this in SQL Server, as in expanding my SP to return a lot fewer but "wider" rows with a lot more columns?
Another complication is that one inspection may have rows which another one lacks, in that case the solution is to add an empty value or a '-'.
P.S. This is using Sql Server 2012.
If you are using mssql 2005+. You can use a pivot like this:
Test data
DECLARE #tbl TABLE(Inspection INT, [Desc] VARCHAR(100),Value INT)
INSERT INTO #tbl
VALUES
(1,'Description1', 3),
(1,'Description2', 2),
(1,'Description3', 5)
Query
SELECT
*
FROM
(
SELECT
tbl.Inspection,
tbl.[Desc],
tbl.Value
FROM
#tbl AS tbl
) AS tbl
PIVOT
(
SUM(Value)
FOR [Desc] IN ([Description1],[Description2],[Description3])
)AS pvt
Result:
Inspection, Description1, Description2, Description3
1 3 2 5
Edit
As juharr said in the comment:
The resulting column names (values in the table) are when building the query. Which might require another initial query to get
Edit 2
If you are not using mssql 2005+. Or want to have and alternitive explanation. Please see the following query:
SELECT
tbl.Inspection,
SUM(CASE WHEN [Desc]='Description1' THEN tbl.Value ELSE 0 END) AS Description1,
SUM(CASE WHEN [Desc]='Description2' THEN tbl.Value ELSE 0 END) AS Description2,
SUM(CASE WHEN [Desc]='Description3' THEN tbl.Value ELSE 0 END) AS Description3
FROM
#tbl AS tbl
GROUP BY
tbl.Inspection
This do not requiere a pivot and can be use on most of RDMS out there
You should use Sql Server Pivot. It converts rows into columns. You can have an easiest start by this example.
If you'd like to do this dynamically, without having to know what all of the Desc values are, you can build your pivot query and use Exec() or Execute sp_executesql
DECLARE #Columns NVARCHAR(MAX),
#Sql NVARCHAR(MAX)
--Build your column headers based on Distinct Desc values
SELECT #Columns = COALESCE(#Columns + ',', '') + QUOTENAME([Desc])
FROM (SELECT DISTINCT [Desc] FROM tbl) t
ORDER BY [Desc]
--Build your pivot query
SET #Sql = '
SELECT
*
FROM
tbl
PIVOT
(
MAX([Value])
FOR [Desc] IN (' + #Columns + ')
) p
'
EXEC(#Sql)
If you want - for null values, you'll need to create another variable to hold the conversion scripts for the Select part of your sql.
DECLARE #Columns NVARCHAR(MAX),
#Sql NVARCHAR(MAX),
#ColumnAliases NVARCHAR(MAX)
--Build your pivot columns based on Distinct Desc values
SELECT #Columns = COALESCE(#Columns + ',', '') + QUOTENAME([Desc])
FROM (SELECT DISTINCT [Desc] FROM tbl) t
ORDER BY [Desc]
--Build your column headers, replacing NULL with -
SELECT #ColumnAliases = COALESCE(#ColumnAliases + ',', '')
+ 'COALESCE(CONVERT(VARCHAR,' + QUOTENAME([Desc]) + '),''-'') AS ' + QUOTENAME([Desc])
FROM (SELECT DISTINCT [Desc] FROM tbl) t
ORDER BY [Desc]
--Build your pivot query
SET #Sql = '
SELECT
Inspection,'
+ #ColumnAliases + '
FROM
tbl
PIVOT
(
MAX([Value])
FOR [Desc] IN (' + #Columns + ')
) p
'
EXEC(#Sql)

Stored Procedure giving different result on same database with same argument

I have a stored procedure which gives different result in only a specific case.
When I call it from SQL Server Management Studio 2008 R2, it gives me 0 as output.
When I call it from C# class file. It gives me 1 as output.
I am using edmx file, and it is updated for sure.
The call is something like below from SSMS [SQL Server Management Studio]
exec proc_GetPrimaryKeyUsageCount 62, 'tblFormula'
This gives output as 0
The same stored procedure is called from C# file is like below
_db.GetPrimaryKeyUsageCount(62, "tblFormula");
This gives output as 1
The stored procedure is
CREATE PROCEDURE proc_GetPrimaryKeyUsageCount (
#PrimaryKeyColumnId INT
,#PrimaryKeyTable NVARCHAR(max)
--,#Response INT OUTPUT
)
AS
BEGIN
DECLARE #counter INT
DECLARE #sqlCommand NVARCHAR(max)
DECLARE #ForeignKey TABLE (
child_table VARCHAR(max)
,child_fk_column VARCHAR(max)
)
DECLARE #child_table VARCHAR(max)
DECLARE #child_fk_column VARCHAR(max)
SET #counter = 0
INSERT INTO #ForeignKey
SELECT child_table = c.TABLE_NAME
,child_fk_column = c.COLUMN_NAME
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE p
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS pc ON pc.UNIQUE_CONSTRAINT_SCHEMA = p.CONSTRAINT_SCHEMA
AND pc.UNIQUE_CONSTRAINT_NAME = p.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE c ON c.CONSTRAINT_SCHEMA = pc.CONSTRAINT_SCHEMA
AND c.CONSTRAINT_NAME = pc.CONSTRAINT_NAME
WHERE EXISTS (
SELECT 1
FROM INFORMATION_SCHEMA.COLUMNS
WHERE COLUMN_NAME = 'IsDeleted'
AND TABLE_SCHEMA = p.TABLE_SCHEMA
AND TABLE_NAME = p.TABLE_NAME
AND p.TABLE_NAME = #PrimaryKeyTable
)
DECLARE db_cursor CURSOR
FOR
SELECT child_table
,child_fk_column
FROM #ForeignKey
OPEN db_cursor
FETCH NEXT
FROM db_cursor
INTO #child_table
,#child_fk_column
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT 'select count(*) from ' + CAST(#child_table AS VARCHAR) + ' where ' + CAST(#child_fk_column AS VARCHAR) + ' = ' + CAST(#PrimaryKeyColumnId AS VARCHAR)
SET #sqlCommand = 'select #cnt=count(*) from ' + CAST(#child_table AS VARCHAR) + ' where ' + CAST(#child_fk_column AS VARCHAR) + ' = ' + CAST(#PrimaryKeyColumnId AS VARCHAR)
EXEC sp_executesql #sqlCommand
,N'#cnt int OUTPUT'
,#cnt = #counter OUTPUT
IF #counter > 0
BREAK
FETCH NEXT
FROM db_cursor
INTO #child_table
,#child_fk_column
END
SELECT #counter AS [PrimaryKeyUsageCount]
END
1st argument is Id of the primary key and 2nd argument is the name of the table having that primary key.
The Procedure returns the count of the usage of primary key in other tables in same database. If it finds even 1 occurrence, it will return that count otherwise 0.
If anything extra is needed please do let me know.
There are couple of mistakes, which could cause the problem.
The INSERT should be like that:
INSERT INTO #ForeignKey
SELECT c.TABLE_NAME,c.COLUMN_NAME
FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE p
INNER JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS pc ON pc.UNIQUE_CONSTRAINT_SCHEMA = p.CONSTRAINT_SCHEMA
AND pc.UNIQUE_CONSTRAINT_NAME = p.CONSTRAINT_NAME
INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE c ON c.CONSTRAINT_SCHEMA = pc.CONSTRAINT_SCHEMA
AND c.CONSTRAINT_NAME = pc.CONSTRAINT_NAME
WHERE EXISTS (
SELECT *
FROM INFORMATION_SCHEMA.COLUMNS AS isc
WHERE isc.COLUMN_NAME = 'IsDeleted'
AND isc.TABLE_SCHEMA = p.TABLE_SCHEMA
AND isc.TABLE_NAME = p.TABLE_NAME
AND p.TABLE_NAME = #PrimaryKeyTable
)
After cursor loop shoud be:
CLOSE db_cursor
DEALLOCATE db_cursor

Query works but when put it in stored procedure doesn't work

I test below query and works properly
select * from tbl where userName in ('A','B','C')
but when I create a stored procedure and pass the parameter to it doesn't work (the parameter is correct)
SqlCommand sc = new SqlCommand("exec example #userName",...
sc.Parameters.AddWithValue("#userName", "'A','B','C'");
...
// Stored Procedure
create procedure example
#users varchar(100)
as
select * from tbl where userName in ('#users')
Don't put the argument in quotes.
However, just having:
select * from tbl where userName in (#users)
Won't work either as then you are trying to use the in function on a string that happens to represent a list of elements.
You'll need to convert the string to an array and then use that in the select statement:
The MSDN has an example function for doing this:
CREATE FUNCTION dbo.Split
( #Delimiter varchar(5),
#List varchar(8000)
)
RETURNS #TableOfValues table
( RowID smallint IDENTITY(1,1),
[Value] varchar(50)
)
AS
BEGIN
DECLARE #LenString int
WHILE len( #List ) > 0
BEGIN
SELECT #LenString =
(CASE charindex( #Delimiter, #List )
WHEN 0 THEN len( #List )
ELSE ( charindex( #Delimiter, #List ) -1 )
END
)
INSERT INTO #TableOfValues
SELECT substring( #List, 1, #LenString )
SELECT #List =
(CASE ( len( #List ) - #LenString )
WHEN 0 THEN ''
ELSE right( #List, len( #List ) - #LenString - 1 )
END
)
END
RETURN
END
Then use it like this:
select * from tbl where userName in (SELECT * FROM dbo.Split( ',', #users ))
"in" queries are notoriously hard to parameterise. Firstly, don't put the parameter in quotes - then it isn't a parameter: it is a character literal. But: you also can't just use a single parameter - as that would be searching for a single value that simply has quotes and commas in - not what you intended.
There are various approaches here; for example using a "split" function at the server and inner joining to that result. However, some tools also help. For example, "dapper" allows a syntax tweak:
var names = new List<string> { "A", "B", "C" };
var users = connection.Query<User>(
"select * from users where name in #names",
new { names }).ToList();
The library expands this into a correct "in" query using multiple parameters.

Working with arrays in SQL Server

I have a int array of ID's (a lot of checkboxes I can choose from) which I want to get in one database call though a stored procedure.
Is there a way to work with an array of these ID's in SQL Server? I believe it should be something with splitting the array and then loop it (in sql). I just don't know how?
SQL Server 2008
There are many ways to do this:
Pass in a varchar parameter of the values separated by commas and parse that out (not very efficient, but for a small amount of data, not too bad except for the parsing bit)
Pass in XML and use the built in XML functions (SQL Server 2005+ has better support for this than earlier versions)
Use table value parameters (SQL Server 2008+)
Since you are using SQL Server 2008, use table value parameters.
EDIT: Example below
As #Oded mentioned, table valued parameters is the best option.
However, if for some reason you can't use these (perhaps your calling framework's limitations), you can use the following to perform the split to table:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[SplitToTable]
(
#List varchar(max), #Delim varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH csvtbl(Start, [Stop]) AS (
SELECT Start = convert(bigint, 1), [Stop] =
charindex(#Delim COLLATE Slovenian_BIN2, #list + #Delim)
UNION ALL
SELECT Start = [Stop] + 1, [Stop] = charindex(#Delim
COLLATE Slovenian_BIN2, #list + #Delim, [Stop] + 1)
FROM csvtbl
WHERE ([Stop] > 0)
)
SELECT substring(#list, Start, CASE WHEN [Stop] > 0 THEN [Stop] -
Start ELSE 0 END) AS Value
FROM csvtbl
WHERE ([Stop] > 0)
)
You need to be aware of the default recursion depth of 100. If this isn't enough, increase it by adding the following to your outer calling query:
OPTION (MAXRECURSION 1000) -- or 0 for unlimited
EXAMPLE
SELECT *
FROM MyTable as t
WHERE t.ID IN (
SELECT *
FROM dbo.SplitToTable('1,2,12,34,101', ',')
)
It can be used on joins, etc., too.
I think you need something like...
Declare #query as varchar(500)
Declare #valuesList as varchar(100)
set #valuesList = '1,2,3'
set #query = 'select * From tableName where id in ( ' + #valuesList + ')'
exec(#query)
TO REVERSE THE PROCESS
DECLARE #t TABLE
(
ID int
)
INSERT INTO #t
VALUES (1), (3), (5), (7), (9)
SELECT STUFF(
(
SELECT ',' + CAST(t.ID AS VARCHAR(10))
FROM #t t
FOR XML PATH('')
), 1, 1, '') AS CSV
Courtesy SQLAuthority.

Categories

Resources