I have a stored procedure where I receive data as JSON from a API in C#. I insert the data into two tables like this:
INSERT INTO dbo.ServiceRequestHeader(SubscriptionId, CustomerAccountId, ModifiedBy)
OUTPUT Inserted.ServiceRequestHeaderId INTO #TempT
SELECT
SubscriptionId,
CustomerAccountId,
ModifiedBy
FROM
OpenJson(#JsonServiceRequest)
WITH
(SubscriptionId TinyInt,
CustomerAccountId Int)
SELECT #TempId = Id FROM #TempT
INSERT INTO dbo.ServiceRequest(ServiceRequestId, ServiceRequestHeaderId, SubscriptionId)
SELECT
#TempId, -- <= Here I need to modify the serviceRequestHeaderId
#TempId,
SubscriptionId
FROM
OpenJson(#JsonServiceRequest, '$.ServiceRequest')
WITH (SubscriptionId TinyInt,
...)
The thing is that the serviceRequestId is not a calculated field and it's a special case that depends on ServiceRequestHeaderId.
Example:
If ServiceRequestHeaderId = 1000 the ServiceRequestId would be 1000 001, 1000 002... N...
This is where I can't come with a way to do it
You can generate servicerequestids as given below. I am using FORMAT function with 000 for padding with 0 till 3 digits. If you want four digits, use 0000.
SELECT #TempId = Id FROM #TempT
INSERT INTO dbo.ServiceRequest(ServiceRequestId, ServiceRequestHeaderId, SubscriptionId)
SELECT
CONCAT(#TempId,FORMAT(ROW_NUMBER() OVER(ORDER BY (SELECT null)),'000')) AS ServiceRequestId, -- <= Here I need to modify the serviceRequestHeaderId
#TempId,
SubscriptionId
FROM
OpenJson(#JsonServiceRequest, '$.ServiceRequest')
WITH (SubscriptionId TinyInt,
...)
You will get something like below:
+------------------+
| ServiceRequestId |
+------------------+
| 1000001 |
| 1000002 |
| 1000003 |
+------------------+
Use a CTE to calculate a row number per request and then build the id from it e.g.
with MyCTE as (
select
SubscriptionId
-- Order by whatever makes business sense to you
, row_number() over (order by SubscriptionId) rn
from openjson(#JsonServiceRequest, '$.ServiceRequest')
with (
SubscriptionId tinyint,
...
)
)
insert into dbo.ServiceRequest (ServiceRequestId, ServiceRequestHeaderId, SubscriptionId)
-- Put whatever logic you like here to calculate a row number based id
select convert(varchar(4),#TempId) + ' ' + case when rn >= 100 then convert(varchar(3),rn) when rn > 10 then '0' + convert(varchar(2),rn) else '00' + convert(varchar(1),rn) end
, #TempId, SubscriptionId
from MyCTE;
Related
I have a number into variable like:
DECLARE #CurrentLastFolio INT = (SELECT TOP 1 Folio FROM myTable ORDER BY folio DESC)
So supposing we have number 1004 into this variable.
Into another query I have some rows with folio = 0:
SELECT * FROM myTable WHERE folio = 0
That I want to do is to update this records with #CurrentLastFolio + 1 on each row. So first row should have Folio = 1005, next 1006 and so on.
How can I achieve that?
You can use the following solution, updating the variable on the UPDATE too:
DECLARE #CurrentLastFolio INT = (SELECT MAX(folio) FROM table_name);
UPDATE table_name
SET folio = #CurrentLastFolio, #CurrentLastFolio += 1
WHERE folio = 0
demo on dbfiddle.uk
Arithmetic overflow error converting expression to data type int:
In case your values get larger than 2,147,483,647 (maximum value of INT data type) you need to use BIGINT intead of INT to store (column folio) or increment the value (variable #CurrentLastFolio):
-- you need to use BIGINT on the table.
CREATE TABLE table_name (
folio BIGINT
)
-- you need to use BIGINT on the variable.
DECLARE #CurrentLastFolio BIGINT = (SELECT MAX(folio) from table_name);
-- now you can count and store numbers larger than INT maximum value.
UPDATE table_name
SET folio = #CurrentLastFolio, #CurrentLastFolio += 1
WHERE folio = 0
demo on dbfiddle.uk
Check this:
declare #CurrentLastFolio int = (SELECT TOP 1 Folio from myTable order by folio desc);
with tbl AS
(
SELECT * ,ROW_NUMBER() OVER( order by c1 ) AS rn
FROM myTable
)
update tbl set folio = rn + #x
Example Implementation for easy understanding
create table t (c1 int);
GO
✓
insert into t values (1), (2), (3)
GO
3 rows affected
declare #x int = 1000;
with tbl AS
(
SELECT * ,ROW_NUMBER() OVER( order by c1 ) AS rn
FROM t
)
update tbl set c1 = rn + #x
GO
3 rows affected
select * from t
GO
| c1 |
| ---: |
| 1001 |
| 1002 |
| 1003 |
db<>fiddle here
I am string splitting some values that are comma delimited into rows.
However some values have an extra comma on the end.
Example
Userid | Value
1 | A,B,C,D,
2 | F,H
Code
select value
from string_split('A,B,C,D,',',')
Current Output
UserId | Value
1 | A
1 | B
1 | C
1 | D
1 |
Is there any way to make the string split function ignore the final comma if no data follows it?
Desired Output
UserId | Value
1 | A
1 | B
1 | C
1 | D
Using MSSQL
Just add "WHERE" sentence like this:
select value
from string_split('A,B,C,D,',',')
WHERE value <> ''
STRING_SPLIT Function doesn't support for lower version of sql server so first create a function to split the given string and join the function with your select query.Here is below sample for your expected result.
Created User defined Function
CREATE FUNCTION [dbo].[Udf_StringSplit]
(
#Userid INT,
#Value VARCHAR(1000)
)
RETURNS #Result TABLE(
Userid INT,
Value VARCHAR(10)
)
AS BEGIN
DECLARE #Data AS TABLE
(
Userid INT,
Value VARCHAR(100)
)
INSERT INTO #Data(Userid,Value)
SELECT #Userid, #Value
INSERT INTO #Result(Userid,Value)
SELECT Userid,
Split.a.value('.','nvarchar(1000)') AS Value
FROM
(
SELECT Userid,
CAST('<S>'+REPLACE(#Value,',','</S><S>')+'</S>' AS XML) Value
FROM #Data
) AS A
CROSS APPLY Value.nodes('S') AS Split(a)
WHERE Userid=#Userid AND Split.a.value('.','nvarchar(1000)') <>''
RETURN
END
GO
Sample data table
DECLARE #Data AS TABLE(Userid INT , Value VARCHAR(100))
INSERT INTO #Data
SELECT 1,'A,B,C,D,' UNION ALL
SELECT 2,'F,H'
Sql script to get the expected result
SELECT d.Userid,
f.Value
FROM #Data d
CROSS APPLY [dbo].[Udf_StringSplit] (d.Userid,d.Value) AS f
WHERE d.Userid=1
GO
Result
Userid Value
------------
1 A
1 B
1 C
1 D
Now I'm sure this has been asked and superbly been answered on here. However, I am unable to find the answer since it touches many keywords.
I basically want to replace a table of the form:
Type amount param note
7 2 str1 NULL
42 12 str2 NULL
128 7 str3 samplenote
42 12 NULL NULL
101 4 str4 NULL
42 12 NULL NULL
7 1 str1 samplenote
128 2 str5 NULL
with a table like:
Type amount param note
7 3 str1 combined
42 36 NULL combined
128 9 NULL combined
101 4 str4 combined
In words, I seek to sum up the amount parameter based on its type while declaring param = NULL for all "unclear" fields. (param should be NULL when the param values of combined Types have more than one different content; else, param should have the original content.)
With my python background, I tackled this task with a for loop approach, iterating through the types, adding a new row for every type with summed up amount and note = 'combined', to then delete the remaining rows (see below). There has to be a more efficient way with some JOIN statement I'm sure. But how would that look like?
FYI, this is the solution I am working on (not functioning yet!):
/*** dbo.sourcetable holds all possible Type values ***/
CREATE PROCEDURE [sumup]
AS
BEGIN
DECLARE #i int = (SELECT TOP (1) Type FROM [dbo].[sourcetable] ORDER BY Type)
DECLARE #MaxType int = (SELECT TOP (1) Type FROM [dbo].[sourcetable] ORDER BY Type DESC)
DECLARE #sum int
BEGIN TRY
WHILE #i <= #MaxType
BEGIN
IF EXISTS (SELECT * FROM [dbo].[worktable] WHERE Type = #i)
BEGIN
SET #sum = (SELECT SUM(amount) FROM [dbo].[worktable] WHERE Type = #i)
BEGIN
WITH cte AS (SELECT * FROM [dbo].[worktable] WHERE Type = #i)
INSERT INTO [dbo].[worktable]
([Type]
,[amount]
,[param]
,[note]
SELECT
cte.Type
,#sum
,cte.param
,'combined'
FROM cte
END
DELETE FROM [dbo].[worktable] WHERE Type = #i AND ISNULL([note],'') <> 'combined'
END
SET #i = #i + 1
END
END TRY
BEGIN CATCH
-- some errorlogging code
END CATCH
END
GO
This can be achieved with a single select statement.
If you require your combined flag to only apply to where more than one row has been combined, add another case expression checking the result of either a count(1) for rows combined or count(distinct param) for unique param values combined:
declare #t as table(type int, amount int, param varchar(15), note varchar(15));
insert into #t values (7,2,'str1',NULL),(42,12,'str2',NULL),(128,7,'str3','samplenote'),(42,12,NULL,NULL),(101,4,'str4',NULL),(42,12,NULL,NULL),(7,1,'str1','samplenote'),(128,2,'str5',NULL);
select type
,sum(amount) as amount
,case when count(distinct isnull(param,'')) = 1
then max(param)
else null
end as param
,'combined' as note
from #t
group by type
order by type;
Output:
+------+--------+-------+----------+
| type | amount | param | note |
+------+--------+-------+----------+
| 7 | 3 | str1 | combined |
| 42 | 36 | NULL | combined |
| 101 | 4 | str4 | combined |
| 128 | 9 | NULL | combined |
+------+--------+-------+----------+
I am doing this way from keyboard, but this may work or be close to what you want
Select type , amount , iif( dc=1,p,null) param, 'combined' note
from
(
Select type, sum(amount) amount,
count(distinct Param) dc,max(Param) p
From ....
Group by type
) x
Here is a possible solution:
declare #tbl as table (
type int
,amount int
,param varchar(15)
,note varchar(15)
)
insert into #tbl values (7,2,'str1',NULL)
insert into #tbl values (42,12,'str2',NULL)
insert into #tbl values (128,7,'str3','samplenote')
insert into #tbl values (42,12,NULL,NULL)
insert into #tbl values (101,4,'str4',NULL)
insert into #tbl values (42,12,NULL,NULL)
insert into #tbl values (7,1,'str1','samplenote')
insert into #tbl values (128,2,'str5',NULL)
;WITH CTE AS (
SELECT
type
,SUM(AMOUNT) AS amount
,COUNT(DISTINCT ISNULL(param, 'dummy value')) AS ParamNo
,MAX(Param) AS Param
FROM #tbl
GROUP BY type
) SELECT
type
,amount
,CASE WHEN ParamNo = 1 THEN Param ELSE NULL END AS Param
,'combined' AS note
FROM CTE
This should work:
Select Type, sum(amount) as amount, count(distinct param)
, case when count(distinct param) = 1 then max(param) end as param,
'Combined' as note
From
mytable
Group By Type
I sure hope someone can help me out with this issue. I have been searching for hours to find it but I am coming up empty.
In this example I have two columns in my table
GRP_ID Desc
My group ID is the way I will identify that these products are of the same type, and desc is what I want to find all the common words.
So here is my table
GRP_ID Desc
-------------------------------
2 Red Hat
2 Green Hat
2 Yellow Hat
3 Boots Large Brown
3 Boots Medium Red
3 Boots Medium Brown
What I want as a result of the query would be the following
GRP_ID Desc
-----------------------
2 Hat
3 Boots
So what I want is all the words that appear in every string in the group or the common words in the group.
I think you'd need to create a mapping table for GRP_ID and products - e.g. Hat and Boots.
CREATE TABLE GroupProductMapping (
GRP_ID INT NOT NULL, -- I'm assuming its an Int
ProductDesc VARCHAR(50) NOT NULL
)
SELECT a.GRP_ID,
b.ProductDesc Desc
FROM {Table_Name} a
INNER JOIN GroupProductMapping b ON a.GRP_ID = b.GRP_ID
Alternatively, if you don't have too many products. You could use CASE in your SELECT clause.
e.g.
SELECT
GRP_ID,
CASE GRP_ID
WHEN 1 THEN 'Hat'
WHEN 2 THEN 'Boots'
END AS Desc
FROM {Table_Name}
{Table_Name} is the name of your original table.
Ideally you would normalise your data and store the words in a separate table.
However for your immediate requirements, you first need to provide a UDF to split 'desc' into words. I poached this function:
-- this function splits the provided strings on a delimiter
-- similar to .Net string.Split.
-- I'm sure there are alternatives (such as calling string.Split through
-- a CLR function).
CREATE FUNCTION [dbo].[Split]
(
#RowData NVARCHAR(MAX),
#Delimeter NVARCHAR(MAX)
)
RETURNS #RtnValue TABLE
(
ID INT IDENTITY(1,1),
Data NVARCHAR(MAX)
)
AS
BEGIN
DECLARE #Iterator INT
SET #Iterator = 1
DECLARE #FoundIndex INT
SET #FoundIndex = CHARINDEX(#Delimeter,#RowData)
WHILE (#FoundIndex>0)
BEGIN
INSERT INTO #RtnValue (data)
SELECT
Data = LTRIM(RTRIM(SUBSTRING(#RowData, 1, #FoundIndex - 1)))
SET #RowData = SUBSTRING(#RowData,
#FoundIndex + DATALENGTH(#Delimeter) / 2,
LEN(#RowData))
SET #Iterator = #Iterator + 1
SET #FoundIndex = CHARINDEX(#Delimeter, #RowData)
END
INSERT INTO #RtnValue (Data)
SELECT Data = LTRIM(RTRIM(#RowData))
RETURN
END
Then you need to split the descriptions and do some grouping (which you would also do if the data was normalised)
-- get the count of each grp_id
with group_count as
(
select grp_id, count(*) cnt from [Group]
group by grp_id
),
-- get the count of each word in each grp_id
group_word_count as
(
select count(*) cnt, grp_id, data from
(
select * from [group] g
cross apply dbo.Split(g.[Desc], ' ')
)
t
group by grp_id, data
)
-- return rows where number of grp_id = number of words in grp_id
select gwc.GRP_ID, gwc.Data [Desc] from group_word_count gwc
inner join group_count gc on gwc.GRP_ID = gc.GRP_ID and gwc.cnt = gc.cnt
Where [Group] is your table.
I have the following tables -
Resource
--------------------
Id, ProjectId, Hours, ApproverId
Project
--------------------
Id, Name
The input is ApproverId. I need to retrieve all the rows that have matching ApproverId (simple enough). And for every resource that I get back, I also need to get their hours (same table) whose approverId is not the one that is passed in (business requirement, to be grayed out in the UI). What I'm doing right now is - get all resources based on ApproverId, stored them in a temp table, then do a distinct on Resource.Id, store it in a different temp table, and then for every Resource.Id, get the rows where the ApproverId is not the one that is passed. Can I combine it all in a single query instead of using temp tables?
Thanks!
Edit: I'm using SQL Server 2008 R2.
Edit 2: Here's my stored procedure. I have changed the logic slightly after reading the comments. Can we get rid of all temp tables and make it faster -
ALTER PROCEDURE GetResourceDataByApprover
#ApproverId UNIQUEIDENTIFIER
AS
CREATE TABLE #Table1
(
Id SMALLINT PRIMARY KEY
IDENTITY(1, 1) ,
ResourceId UNIQUEIDENTIFIER
)
CREATE TABLE #Table2
(
ResourceId UNIQUEIDENTIFIER ,
ProjectId UNIQUEIDENTIFIER ,
ProjectName NVARCHAR(1024)
)
INSERT INTO #Table1
SELECT DISTINCT
ResourceId
FROM dbo.Resource T
WHERE T.ApproverId = #ApproverId
DECLARE #i INT
DECLARE #numrows INT
DECLARE #resourceId UNIQUEIDENTIFIER
SET #i = 1
SET #numrows = ( SELECT COUNT(*)
FROM #Table1
)
IF #numrows > 0
WHILE ( #i <= ( SELECT MAX(Id)
FROM #Table1
) )
BEGIN
SET #resourceId = ( SELECT ResourceId
FROM #Table1
WHERE Id = #i
)
INSERT INTO #Table2
SELECT
T.ResourceId ,
T.ProjectId ,
P.Name AS ProjectName
FROM dbo.[Resource] T
INNER JOIN dbo.Project P ON T.ProjectId = P.ProjectId
WHERE T.ResourceId = #resourceId
SET #i = #i + 1
END
SELECT *
FROM #Table1
SELECT *
FROM #Table2
DROP TABLE #Table1
DROP TABLE #Table2
This query should return two rows for every resource, one for the specified approver and one for all other approvers.
SELECT
Id,
CASE
WHEN ApproverId=#approverId THEN 'SpecifiedApprover'
ELSE 'OtherApprover'
END AS Approver,
SUM(Hours) AS Hours
FROM Resource
GROUP BY
Id,
CASE
WHEN ApproverId=#approverId THEN 'SpecifiedApprover'
ELSE 'OtherApprover'
END
Do you want to know how concrete Approver wastes his time?
SELECT p.Id, p.Name, SUM(r.Hours) as TotalHours
FROM Resource r
LEFT JOIN Project p
ON r.ProjectId = p.Id
WHERE ApproverId = %ConcreteApproverId%
GROUP BY p.Id, p.Name
HAVING SUM(r.Hours) > 0
This query will produce this table example:
+-----+----------+-------+
| Id | Project | Hours |
+-----+----------+-------+
| 203 | ProjectA | 25 |
| 202 | ProjectB | 34 |
| 200 | ProjectC | 46 |
+-----+----------+-------+