SELECT THAT ALSO SET TO VARIABLE - sql-server

I just got a job to a company to maintain an existing program in the company.
Now I need to get a data from table.
So I need to do a select from a table, the table column dateBegin maybe null maybe not, so if datebegin is null i get the datebegin2. and dateto is added 1 year from the datebegin if is null or <= datebegin.
All I can do now is using case when else to get the right value. But its too long for the code and hard to read. How can I achieve the code right below? thanks for your response...
declare #val smalldatetime
select id
, #val = COALESCE(dateBegin, dateBegin2) as DateBegin
, COALESCE(dateTo, dateadd(Y, 1, #val)) As DateTo
from TblL

You cannot mix this, but you can use APPLY to row-wise compute additional columns. These columns can be used within the statement similar to a variable:
Cannot test this, but this should be equivalent to your attempt:
select id
, A.DateBegin
, COALESCE(dateTo, dateadd(Y, 1, A.DateBegin)) As DateTo
from TblL
CROSS APPLY(SELECT COALESCE(dateBegin, dateBegin2)) AS A(DateBegin)
A working example to illustrate the idea
DECLARE #tbl TABLE(ID INT IDENTITY, SomeString VARCHAR(500));
INSERT INTO #tbl VALUES('Find the #value# between the "#"')
,('One more #example#');
SELECT t.ID
,t.SomeString
,A.FirstHash
,B.SecondHash
,SUBSTRING(t.SomeString,A.FirstHash+1,C.FragmentLength) AS Fragment
FROM #tbl AS t
CROSS APPLY(SELECT CHARINDEX('#',t.SomeString)) AS A(FirstHash)
CROSS APPLY(SELECT CHARINDEX('#',t.SomeString,A.FirstHash+1)) AS B(SecondHash)
CROSS APPLY(SELECT B.SecondHash-A.FirstHash-1) AS C(FragmentLength);
Returns
ID SomeString 1.# 2.# Fragment
1 Find the #value# between the "#" 10 16 value
2 One more #example# 10 18 example
The same query without this trick would be something like this
SELECT t.ID
,t.SomeString
,CHARINDEX('#',t.SomeString)
,CHARINDEX('#',t.SomeString,CHARINDEX('#',t.SomeString)+1)
,SUBSTRING(t.SomeString,CHARINDEX('#',t.SomeString)+1,CHARINDEX('#',t.SomeString,CHARINDEX('#',t.SomeString)+1)-CHARINDEX('#',t.SomeString)-1) AS Fragment
FROM #tbl AS t

Related

Is it possible to use STRING_SPLIT in an ORDER BY clause?

I am trying to order values that are going to be inserted into another table based on their order value from a secondary table. We are migrating old data to a new table with a slightly different structure.
I thought I would be able to accomplish this by using the string_split function but I'm not very skilled with SQL and so I am running into some issues.
Here is what I have:
UPDATE lse
SET Options = a.Options
FROM
dbo.LessonStepElement as lse
CROSS JOIN
(
SELECT
tbl1.*
tbl2.Options,
tbl2.QuestionId
FROM
dbo.TrainingQuestionAnswer as tbl1
JOIN (
SELECT
string_agg((CASE
WHEN tqa.CorrectAnswer = 1 THEN REPLACE(tqa.AnswerText, tqa.AnswerText, '*' + tqa.AnswerText)
ELSE tqa.AnswerText
END),
char(10)) as Options,
tq.Id as QuestionId
FROM
dbo.TrainingQuestionAnswer as tqa
INNER JOIN
dbo.TrainingQuestion as tq
on tq.Id = tqa.TrainingQuestionId
INNER JOIN
dbo.Training as t
on t.Id = tq.TrainingId
WHERE
t.IsDeleted = 0
and tq.IsDeleted = 0
and tqa.IsDeleted = 0
GROUP BY
tq.Id,
tqa.AnswerDisplayOrder
ORDER BY
(SELECT [Value] FROM STRING_SPLIT((SELECT AnswerDisplayOrder FROM dbo.TrainingQuestion WHERE Id = tmq.Id), ','))
) as tbl2
on tbl1.TrainingQuestionId = tbl2.QuestionId
) a
WHERE
a.TrainingQuestionId = lse.TrainingQuestionId
The AnswerDisplayOrder that I am using is just a nvarchar comma separated list of the ids for the answers to the question.
Here is an example:
I have 3 rows in the TrainingQuestionAnswer table that look like the following.
ID TrainingQuestionId AnswerText
-------------------------------------------
215 100 No
218 100 Yes
220 100 I'm not sure
I have 1 row in the TrainingQuestion table that looks like the following.
ID AnswerDisplayOrder
--------------------------
100 "218,215,220"
Now what I am trying to do is when I update the row in the new table with all of the answers combined, the answers will need to be in the correct order which is dependent on the AnswerDisplayOrder in the TrainingQuestion table. So in essence, the new table would have a row that would look similar to the following.
ID Options
--------------
193 "Yes No I'm not sure"
I'm aware that the way I'm trying to do it might not be even possible at all. I am still learning and would just love some advice or guidance on how to make this work. I also know that string_split does not guarantee order. I'm open to other suggestions that do guarantee the order as well.
I simplified the issue in the question to the following approach, that is a possible solution to your problem. If you want to get the results from the question, you need a splitter, that returns the substrings and the positions of the substrings. STRING_SPLIT() is available from SQL Server 2016, but is not an option here, because (as is mentioned in the documentation) the output rows might be in any order and the order is not guaranteed to match the order of the substrings in the input string.
But you may try to use a JSON based approach, with a small string manipulation, that transforms answers IDs into a valid JSON array (218,215,220 into [218,215,220]). After that you can easily parse this JSON array with OPENJSON() and default schema. The result is a table, with columns key, value and type and the key column (again from the documentation) holds the index of the element in the specified array.
Tables:
CREATE TABLE TrainingQuestionId (
ID int,
TrainingQuestionId int,
AnswerText varchar(1000)
)
INSERT INTO TrainingQuestionId
(ID, TrainingQuestionId, AnswerText)
VALUES
(215, 100, 'No'),
(218, 100, 'Yes'),
(220, 100, 'I''m not sure')
CREATE TABLE TrainingQuestion (
ID int,
AnswerDisplayOrder varchar(1000)
)
INSERT INTO TrainingQuestion
(ID, AnswerDisplayOrder)
VALUES
(100, '218,215,220')
Statement:
SELECT tq.ID, oa.Options
FROM TrainingQuestion tq
OUTER APPLY (
SELECT STRING_AGG(tqi.AnswerText, ' ') WITHIN GROUP (ORDER BY CONVERT(int, j.[key])) AS Options
FROM OPENJSON(CONCAT('[', tq.AnswerDisplayOrder, ']')) j
LEFT JOIN TrainingQuestionId tqi ON TRY_CONVERT(int, j.[value]) = tqi.ID
) oa
Result:
ID Options
100 Yes No I'm not sure
Notes: You need SQL Server 2017+ to use STRING_AGG(). For SQL Server 2016 you need a FOR XML to aggregate strings.
declare #TrainingQuestionAnswer table
(
ID int,
TrainingQuestionId int,
AnswerText varchar(20)
);
insert into #TrainingQuestionAnswer(ID, TrainingQuestionId, AnswerText)
values(215, 100, 'No'), (218, 100, 'Yes'), (220, 100, 'I''m not sure');
declare #TrainingQuestiontest table
(
testid int identity,
QuestionId int,
AnswerDisplayOrder varchar(200)
);
insert into #TrainingQuestiontest(QuestionId, AnswerDisplayOrder)
values(100, '218,215,220'), (100, '220,218,215'), (100, '215,218');
select *,
(
select string_agg(pci.AnswerText, '==') WITHIN GROUP ( ORDER BY pci.pos)
from
(
select a.AnswerText,
pos = charindex(concat(',', a.ID, ','), concat(',', q.AnswerDisplayOrder,','))
from #TrainingQuestionAnswer as a
where a.TrainingQuestionId = q.QuestionId
and charindex(concat(',', a.ID, ','), concat(',', q.AnswerDisplayOrder,',')) >= 1
) as pci
) as TestAnswerText
from #TrainingQuestiontest as q;

SQL combine multiple records into one row

I have a table pulling userid's and their personal and work e-mails. I'd like to have one line per id showing both types of e-mails, but I can't figure out how to do that.
declare #t table(NPI int, email varchar(50), emailtype varchar(50))
insert into #t
values(1, 'john#home', 'personal'), (1, 'john#work', 'work');
This is the query I've written so far, which puts this on 2 separate rows:
select npi, case when emailtype = 'personal' then email end as personalemail,
case when emailtype = 'work' then email end as workemail
from #t;
Current Output:
npi personalemail workemail
1 john#home NULL
1 NULL john#work
What I'd like to see is:
npi personalemail workemail
1 john#home john#work
How can I do this?
This has been asked and answered around here about a million times. It is called conditional aggregation or crosstab. It is faster to write an answer than find one. As an alternative you could use PIVOT but I find the syntax a bit obtuse for me.
select NPI
, max(case when emailtype = 'personal' then email end) as PersonalEmail
, max(case when emailtype = 'work' then email end) as WorkEmail
from #t
group by NPI
Use pivot
SELECT
*
FROM #T
PIVOT
(
MAX(email)
FOR EmailType IN
(
personal,work
)
)Q

Conversion failed when converting the nvarchar value 'test' to data type int

Context: SQL Server 2008
I have a table mytable which contains two NVARCHAR columns id, title.
All data in the id column are in fact numeric, except one row which contains the value 'test'.
I want to get all ids between 10 and 15 so I need SQL Server to convert id column values to INTEGER.
I use ISNUMERIC(id) = 1 to eliminate the non numeric values first but SQL Server is being rather weird with this query.
SELECT
in.*
FROM
(SELECT
id, title
FROM
mytable
WHERE
ISNUMERIC(id) = 1) in
WHERE
in.id BETWEEN 10 AND 15
This query causes the following error:
Conversion failed when converting the nvarchar value 'test' to
data type int.
The inner query eliminates the row with the 'test' id value so 'in' shouldn't contain it. Why is SQL Server still trying to convert it?
Am I missing something? How can I get around this?
P.S. I tried WHERE CAST(in.id AS INTEGER) BETWEEN 10 AND 15 but didn't work either.
Use TRY_CONVERT function, it's very handy.
SELECT id,
title
FROM mytable
where TRY_CONVERT(int, id) is not NULL
and TRY_CONVERT(int, id) BETWEEN 10 and 15
TRY_CONVERT returns null if the conversion fails.
And for you error, I suppose that the Query Optimizer messes something up here. Take a look at the execution plan, maybe it's filtering values between 10 and 15 at the first place. My solution will always work.
As the other commenter said in your case the BETWEEN function is done before ISNUMERIC. Here is a simple example:
select * into #temp2
from (
select 'test' a
union
select cast(1 as varchar) a
union
select cast(2 as varchar) a
union
select cast(3 as varchar) a
)z
SELECT a FROM (
SELECT a FROM #temp2 WHERE ISNUMERIC(a) = 1
) b
WHERE b.a BETWEEN 10 AND 15
This simple query is an alternative:
SELECT a
FROM #temp2
WHERE ISNUMERIC(a) = 1
and a BETWEEN 10 AND 15
I should add one more way with XML style:
SELECT *
FROM mytable
WHERE CAST(id as xml).value('. cast as xs:decimal?','int') BETWEEN 10 AND 15
Convert id to XML, convert the value in xs:decimal and then convert to integer. If there is not numeric value it will be converted into NULL.
Here you can read about XML type casting rules (link).
Could create a new field to do the search on:
Select id, title
from (Select id, title, case id when 'test' then null else cast(id as int) end as trueint from mytable) n
where n.trueint between 10 and 15
OR
Select id, title
from mytable
where case isnumeric(id) when 1 then cast(id as int) else null end between 10 and 15
One possibility is to force the engine to work this in two steps:
DECLARE #tbl TABLE(id NVARCHAR(MAX),title NVARCHAR(MAX));
INSERT INTO #tbl
SELECT id, title FROM mytable WHERE ISNUMERIC(id) = 1;
SELECT t.*
FROM #tbl t
WHERE CAST(t.id AS INT) BETWEEN 10 AND 15

How to check for a specific condition by looping through every record in SQL Server?

I do have following table
ID Name
1 Jagan Mohan Reddy868
2 Jagan Mohan Reddy869
3 Jagan Mohan Reddy
Name column size is VARCHAR(55).
Now for some other task we need to take only 10 varchar length i.e. VARCHAR(10).
My requirement is to check that after taking the only 10 bits length of Name column value for eg if i take Name value of ID 1 i.e. Jagan Mohan Reddy868 by SUBSTRING(Name, 0,11) if it equals with another row value. here in this case the final value of SUBSTRING(Jagan Mohan Reddy868, 0,11) is equal to Name value of ID 3 row whose Name is 'Jagan Mohan Reddy'. I need to make a list of those kind rows. Can somebody help me out on how can i achieve in SQL Server.
My main check is that the truncated values of my Name column should not match with any non truncated values of Name column. If so i need to get those records.
Assuming I understand the question, I think you are looking for something like this:
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(15)
)
INSERT INTO #T VALUES
('Hi, I am Zohar.'),
('Hi, I am Peled.'),
('Hi, I am Z'),
('I''m Zohar peled')
Use a cte with a self inner join to get the list of ids that match the first 10 chars:
;WITH cte as
(
SELECT T2.Id As Id1, T1.Id As Id2
FROM #T T1
INNER JOIN #T T2 ON LEFT(T1.Name, 10) = t2.Name AND T1.Id <> T2.Id
)
Select the records from the original table, inner joined with a union of the Id1 and Id2 from the cte:
SELECT T.Id, Name
FROM #T T
INNER JOIN
(
SELECT Id1 As Id
FROM CTE
UNION
SELECT Id2
FROM CTE
) U ON T.Id = U.Id
Results:
Id Name
----------- ---------------
1 Hi, I am Zohar.
3 Hi, I am Z
Try this
SELECT Id,Name
FROM(
SELECT *,ROW_NUMBER() OVER(PARTITION BY Name, LEFT(Name,11) ORDER BY ID) RN
FROM Tbale1 T
) Tmp
WHERE Tmp.RN = 1
loop over your column for all the values and put your substring() function inside this loop and I think in Sql index of string starts from 1 instead of 0. If you pass your string to charindex() like this
CHARINDEX('Y', 'Your String')
thus you will come to know whether it is starting from 0 or 1
and you can save your substring value as value of other column with length 10
I hope it will help you..
I think this should cover all the cases you are looking for.
-- Create Table
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(55)
)
-- Create Data
INSERT INTO #T VALUES
('Jagan Mohan Reddy868'),
('Jagan Mohan Reddy869'),
('Jagan Mohan Reddy'),
('Mohan Reddy'),
('Mohan Reddy123551'),
('Mohan R')
-- Get Matching Items
select *, SUBSTRING(name, 0, 11) as ShorterName
from #T
where SUBSTRING(name, 0, 11) in
(
-- get all shortnames with a count > 1
select SUBSTRING(name, 0, 11) as ShortName
from #T
group by SUBSTRING(name, 0, 11)
having COUNT(*) > 1
)
order by Name, LEN(Name)

Speeding up my query using sql server 2008.Alternative to cross apply

I have a function that is performing very slow.I am working a database that I need to migrate data and I have NO control over!.
Ideally I would like to use a view directly since this function is called by a view ,but I could only seem to be able to do it by calling a function.
===
A view should return whatever is in the dummytable by orderNo.If an orderNo has a paymenttype of "Interest" than the balance should be interest,if "tax" should be tax
In real life I will have 200000 rows and more,by using cross apply it seems to slow down quite a lot.
Is there a better way to get the data rather than using CrossApply?
Noddy Sample here(data and datatypes are just fictious for semplicity of the example)
CREATE DATABASE DummyDB
GO
use DummyDB
IF object_id(N'DummyTable', 'U') IS NOT NULL
DROP TABLE DummyTable
GO
CREATE TABLE DummyTable
(
Id int,
OrderNo varchar(255),
PaymentType varchar(255),
Credit varchar(255),
Debit varchar(255),
Balance varchar(255)
)
GO
INSERT INTO [dbo].[DummyTable]([Id], [OrderNo], [PaymentType], [Credit], [Debit], [Balance])
SELECT 1, N'200', N'Interest', N'10', N'5', N'5' UNION ALL
SELECT 2, N'201', N'Deposit', N'400', N'30', N'370' UNION ALL
SELECT 3, N'202', N'Tax', N'20', N'10', N'10' UNION ALL
SELECT 4, N'202', N'Tax', N'50', N'10', N'10'
--my sample attempt not performing
use DummyDB
select * from DummyTable
Declare #OrderNo int
set #OrderNo=202
SELECT
Tax=tx.Tax,
Interest=tx1.Interest,
Deposit=tx2.Deposit
FROM DummyTable T1
CROSS APPLY(SELECT
Tax=sum(cast(T2.Balance as money))
FROM DummyTable T2
WHERE T2.OrderNo=#OrderNo
AND PaymentType='Tax')as tx
CROSS APPLY(select
Interest=sum(cast(T2.Balance as money))
FROM DummyTable T2
WHERE T2.OrderNo=#OrderNo
AND PaymentType='Interest')as tx1
CROSS APPLY(select
Deposit=sum(cast(T2.Balance as money))
FROM DummyTable T2
WHERE T2.OrderNo=#OrderNo
AND PaymentType='Deposit')as tx2
WHERE T1.OrderNo=#OrderNo
Any Suggestion of using something more efficient than cross apply?
Many thanks
This will do almost the same as your sample query. It will give you one row with the result where your query will repeat the values for all rows that match #OrderNo.
select sum(case when T1.PaymentType = 'Tax' then cast(T1.Balance as money) else 0 end) as Tax,
sum(case when T1.PaymentType = 'Interest' then cast(T1.Balance as money) else 0 end) as Interest,
sum(case when T1.PaymentType = 'Deposit' then cast(T1.Balance as money) else 0 end) as Deposit
from DummyTable as T1
where T1.OrderNo = #OrderNo
BTW, you should make sure that the data type for OrderNo in the table is the same as the variable #OrderNo. It looks like you are dealing with integers so you should change the table. If that is not possible for you then you need to change #OrderNo to varchar(255) if you want to use an index on OrderNo.

Resources