Looking for missing gaps for getting "Only one expression can be specified in the select list when the subquery is not introduced with EXISTS." - sql

I have the following query to find missing gaps in the sort for each ModelID but I keep getting the following error and don't know why.
What I'm doing is in my first loop I am looping through the modelID's and in the inner loop I am looking for the missing gaps in the siSort column for that modelID and putting that into a temp table.
Msg 116, Level 16, State 1, Line 27
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
USE crm
GO
BEGIN
DECLARE #ID INT
DECLARE #MAXID INT
DECLARE #COUNT INT
DECLARE #iCustomListModelID INT
DECLARE #iCustomFieldID INT
DECLARE #MissingIds TABLE (ID INT)
DECLARE #Output TABLE (iCustomListModelID INT, siSort INT, iListItemID INT)
-- SELECT ALL DISTINCT ICustomListModelID's FROM CustomList Table
SELECT DISTINCT cl.iCustomListModelID
INTO #DistinctModelIDs
FROM dbo.CustomListModel clm
INNER JOIN dbo.CustomListType clt ON clm.iCustomListTypeID = clt.iCustomListTypeID
AND clt.vchCustomListTypeDescription = N'Household Custom Field'
INNER JOIN dbo.CustomList cl ON clm.iCustomListModelID = cl.iCustomListModelID
INNER JOIN dbo.CustomField cf ON cl.iListItemID = cf.iCustomFieldID
ORDER BY cl.iCustomListModelID
-- Get iCustomFieldID to insert into iListItemID
SET #iCustomFieldID = (SELECT * FROM dbo.CustomField cf WHERE vchLabel = '')
-- Begin Outer loop to go through each iCustomListModelID
WHILE (SELECT COUNT(iCustomListModelID) AS Total FROM #DistinctModelIDs) > 0
BEGIN
-- GRAB THE NEXT iCustomListModelID
SELECT #iCustomListModelID = (SELECT TOP 1 iCustomListModelID FROM #DistinctModelIDs);
DROP TABLE #List
SELECT siSort INTO #List FROM CustomList WHERE iCustomListModelID = #iCustomListModelID
SELECT #MAXID = siSort FROM dbo.CustomList WHERE iCustomListModelID = #iCustomListModelID
SET #ID = 1;
-- Inner loop to go through the missing gaps in siSort
WHILE #ID <= #MAXID
BEGIN
IF NOT EXISTS (
SELECT 'X' FROM #List WHERE siSort = #ID
)
INSERT INTO #MissingIDs (ID)
VALUES (#ID)
--INSERT THE MISSING ID INTO #outputTable Table
INSERT INTO #Output (iCustomListModelID, siSort, iListItemID)
VALUES (#iCustomListModelID, #ID, #iCustomFieldID)
SET #ID = #ID + 1;
END;
-- DELETE CURRENT iCustomListModelID
DELETE FROM #DistinctModelIDs WHERE iCustomListModelID = #iCustomListModelID
END
SELECT * FROM #Output
END;

One possibility is that the issue is this line:
SET #iCustomFieldID = (SELECT * FROM dbo.CustomField cf WHERE vchLabel = '')
If dbo.CustomerField doesn't have exactly one column (more than one column seems likely because vchLabel is already one column in the table), then this will generate an error of that type.

Related

Trigger that prevents update of column based on result of the user defined function

We have DVD Rental company. In this particular scenario we consider only Member, Rental and Membership tables.
The task is to write a trigger that prevents a customer from being shipped a DVD
if they have reached their monthly limit for DVD rentals as per their membership contract using the function.
My trigger leads to infinite loop. It works without While loop, but then it does not work properly, if I consider multiple updates to the Rental table. Where I am wrong?
-- do not run, infinite loop
CREATE OR ALTER TRIGGER trg_Rental_StopDvdShip
ON RENTAL
FOR UPDATE
AS
BEGIN
DECLARE #MemberId INT
DECLARE #RentalId INT
SELECT * INTO #TempTable FROM inserted
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
IF UPDATE(RentalShippedDate)
BEGIN
IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
BEGIN
ROLLBACK
RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
END;
END;
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
END;
My function looks as follows:
CREATE OR ALTER FUNCTION dvd_numb_left(#member_id INT)
RETURNS #tab_dvd_numb_left TABLE(MemberId INT, Name VARCHAR(50), TotalDvdLeft INT, AtTimeDvdLeft INT)
AS
BEGIN
DECLARE #name VARCHAR(50)
DECLARE #dvd_total_left INT
DECLARE #dvd_at_time_left INT
DECLARE #dvd_limit INT
DECLARE #dvd_rented INT
DECLARE #dvd_at_time INT
DECLARE #dvd_on_rent INT
SET #dvd_limit = (SELECT Membership.MembershipLimitPerMonth FROM Membership
WHERE Membership.MembershipId = (SELECT Member.MembershipId FROM Member WHERE Member.MemberId = #member_id))
SET #dvd_rented = (SELECT COUNT(Rental.MemberId) FROM Rental
WHERE CONCAT(month(Rental.RentalShippedDate), '.', year(Rental.RentalShippedDate)) = CONCAT(month(GETDATE()), '.', year(GETDATE())) AND Rental.MemberId = #member_id)
SET #dvd_at_time = (SELECT Membership.DVDAtTime FROM Membership
WHERE Membership.MembershipId = (SELECT Member.MembershipId FROM Member WHERE Member.MemberId = #member_id))
SET #dvd_on_rent = (SELECT COUNT(Rental.MemberId) FROM Rental
WHERE Rental.MemberId = #member_id AND Rental.RentalReturnedDate IS NULL)
SET #name = (SELECT CONCAT(Member.MemberFirstName, ' ', Member.MemberLastName) FROM Member WHERE Member.MemberId = #member_id)
SET #dvd_total_left = #dvd_limit - #dvd_rented
SET #dvd_at_time_left = #dvd_at_time - #dvd_on_rent
IF #dvd_total_left < 0
BEGIN
SET #dvd_total_left = 0
SET #dvd_at_time_left = 0
INSERT INTO #tab_dvd_numb_left(MemberId, Name, TotalDvdLeft, AtTimeDvdLeft)
VALUES(#member_id, #name, #dvd_total_left, #dvd_at_time_left)
RETURN;
END
INSERT INTO #tab_dvd_numb_left(MemberId, Name, TotalDvdLeft, AtTimeDvdLeft)
VALUES(#member_id, #name, #dvd_total_left, #dvd_at_time_left)
RETURN;
END;
Will be glad for any advice.
Your main issue is that even though you populate #TempTable you never pull any values from it.
CREATE OR ALTER TRIGGER trg_Rental_StopDvdShip
ON RENTAL
FOR UPDATE
AS
BEGIN
DECLARE #MemberId INT, #RentalId INT;
-- Move test for column update to the first test as it applies to the entire update, not per row.
IF UPDATE(RentalShippedDate)
BEGIN
SELECT * INTO #TempTable FROM inserted;
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
-- Actually pull some information from #TempTable - this wasn't happening before
SELECT TOP 1 #RentalID = RentalId, #MemberId = MemberId FROM #TempTable;
-- Select our values to its working
-- SELECT #RentalID, #MemberId;
IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
BEGIN
ROLLBACK
RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
END;
-- Delete the current handled row
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
-- For neatness I always drop temp tables, makes testing easier also
DROP TABLE #TempTable;
END;
END;
An easy way to debug simply triggers like this is to copy the T-SQL out and then create an #Inserted table variable e.g.
DECLARE #Inserted table (RentalId INT, MemberId INT);
INSERT INTO #Inserted (RentalId, MemberId)
VALUES (1, 1), (2, 2);
DECLARE #MemberId INT, #RentalId INT;
-- Move test for column update to the first test as it applies to the entire update, not per row.
-- IF UPDATE(RentalShippedDate)
BEGIN
SELECT * INTO #TempTable FROM #inserted;
WHILE (EXISTS (SELECT RentalId FROM #TempTable))
BEGIN
-- Actually pull some information from #TempTable - this wasn't happening before
SELECT TOP 1 #RentalID = RentalId, #MemberId = MemberId FROM #TempTable;
-- Select our values to its working
SELECT #RentalID, #MemberId;
-- IF (SELECT TotalDvdLeft FROM dvd_numb_left(#MemberId)) <= 0
-- BEGIN
-- ROLLBACK
-- RAISERROR ('YOU HAVE REACHED MONTHLY LIMIT FOR DVD RENTALS', 16, 1)
-- END;
-- Delete the current handled row
DELETE FROM #TempTable WHERE RentalID = #RentalId
END;
-- For neatness I always drop temp tables, makes testing easier also
DROP TABLE #TempTable;
END;
Note: throw is the recommended way to throw an error instead of raiserror.
Another thing to consider is that you must try to transform your UDF into an inline TVF because of some side effects.
Like this one:
CREATE OR ALTER FUNCTION dvd_numb_left(#member_id INT)
RETURNS TABLE
AS
RETURN
(
WITH
TM AS
(SELECT Membership.MembershipLimitPerMonth AS dvd_limit,
Membership.DVDAtTime AS dvd_at_time,
CONCAT(Member.MemberFirstName, ' ', Member.MemberLastName) AS [name]
FROM Membership AS MS
JOIN Member AS M
ON MS.MembershipId = M.MembershipId
WHERE M.MemberId = #member_id
),
TR AS
(SELECT COUNT(Rental.MemberId) AS dvd_rented
FROM Rental
WHERE YEAR(Rental.RentalShippedDate ) = YEAR(GETDATE)
AND MONTH(Rental.RentalShippedDate ) = MONTH(GETDATE)
AND Rental.MemberId = #member_id
)
SELECT MemberId, [Name],
CASE WHEN dvd_limit - dvd_rented < 0 THEN 0 ELSE dvd_limit - dvd_rented END AS TotalDvdLeft,
CASE WHEN dvd_limit - dvd_rented < 0 THEN 0 ELSE dvd_at_time - dvd_on_rent END AS AtTimeDvdLeft
FROM TM CROSS JOIN TR
);
GO
Which will be much more efficient.
The absolute rule to have performances is: TRY TO STAY IN A "SET BASED" CODE instead of iterative code.
The above function can be optimized by the optimzer whilet yours cannot and will needs 4 access to the same tables.

Generating dummy data from existing data set is slow using cursor

I'm trying to generate dummy data from the existing data I have in the tables. All I want is to increase the number of records in Table1 to N specified amount. The other tables should increase based on the foreign key references.
The tables has one to many relationship. For one record in table 1, I can have multiple entries in table 2, and in table 3 I can have many records based on IDs of the second table.
Since IDs are primary keys, I either capture it by
SET #NEWLY_INSERTED_ID = SCOPE_IDENTITY()
after inserting to table 1 and using in insert for table2, or inserting them to temp table and joining them to achieve the same results for table 3.
Here's the approach I'm taking with the CURSOR.
DECLARE #MyId as INT;
DECLARE #myCursor as CURSOR;
DECLARE #DESIRED_ROW_COUNT INT = 70000
DECLARE #ROWS_INSERTED INT = 0
DECLARE #CURRENT_ROW_COUNT INT = 0
DECLARE #NEWLY_INSERTED_ID INT
DECLARE #LANGUAGE_PAIR_IDS TABLE ( LangugePairId INT, NewId INT, SourceLanguage varchar(100), TargetLangauge varchar(100) )
WHILE (#ROWS_INSERTED < #DESIRED_ROW_COUNT)
BEGIN
SET #myCursor = CURSOR FOR
SELECT Id FROM MyTable
SET #CURRENT_ROW_COUNT = (SELECT COUNT(ID) FROM MyTable)
OPEN #myCursor;
FETCH NEXT FROM #myCursor INTO #MyId;
WHILE ##FETCH_STATUS = 0
BEGIN
IF ((#CURRENT_SUBMISSION_COUNT < #DESIRED_ROW_COUNT) AND (#ROWS_INSERTED < #DESIRED_ROW_COUNT))
BEGIN
INSERT INTO [dbo].[MyTable]
([Column1]
([Column2]
([Column3]
)
SELECT
,convert(numeric(9,0),rand() * 899999999) + 100000000
,COlumn2
,Colum3
FROM MyTable
WHERE Id = #MyId
SET #NEWLY_INSERTED_ID = SCOPE_IDENTITY()
INSERT INTO [dbo].[Language]
([MyTable1Id]
,[Target]
,[Source]
OUTPUT inserted.Id, inserted.MyTable1Id, inserted.Source, inserted.[Target] INTO #LANGUAGE_PAIR_IDS (LangugePairId, NewId, SourceLanguage, TargetLangauge)
SELECT
#NEWLY_INSERTED_ID
,[Target]
,[Source]
FROM [dbo].[Language]
WHERE MyTableId = #MyId
ORDER BY Id
DECLARE #tbl AS TABLE (newLanguageId INT, oldLanguageId INT, sourceLanguage VARCHAR(100), targetLanguage VARCHAR(100))
INSERT INTO #tbl (newLanguageId, oldLanguageId, sourceLanguage, targetLanguage)
SELECT 0, id, [Source], [Target] MyTable1Id FROM Language WHERE MyTable1Id = #MyId ORDER BY Id
UPDATE t
SET t.newlanguageid = lp.LangugePairId
FROM #tbl t
JOIN #LANGUAGE_PAIR_IDS lp
ON t.sourceLanguage = lp.SourceLanguage
AND t.targetLanguage = lp.TargetLangauge
INSERT INTO [dbo].[Manager]
([LanguagePairId]
,[UserId]
,[MyDate])
SELECT
tbl.newLanguageId
,p.[UserId]
,p.[MyDate]
FROM Manager m
INNER JOIN #tbl tbl
ON m.LanguagePairId = tbl.oldLanguageId
WHERE m.LanguagePairId in (SELECT Id FROM Language WHERE MyTable1Id = #MyId) -- returns the old language pair id
SET #ROWS_INSERTED += 1
SET #CURRENT_ROW_COUNT +=1
END
ELSE
BEGIN
PRINT 'REACHED EXIT'
SET #ROWS_INSERTED = #DESIRED_ROW_COUNT
BREAK
END
FETCH NEXT FROM #myCursor INTO #MyId;
END
CLOSE #myCursor
DEALLOCATE #myCursor
END
The above code works! It generates the data I need. However, it's very very slow. Just to give some comparison. Initial load of data for table 1 was ~60,000 records, Table2: ~74,000 and Tabl3 ~3,400
I tried to insert 9,000 rows in Table1. With the above code, it took 17:05:01 seconds to complete.
Any suggestion on how I can optimize the query to run little faster? My goal is to insert 1-2 mln records in Table1 without having to wait for days. I'm not tied to CURSOR. I'm ok to achieve the same result in any other way possible.

SQL Server generate script for views and how to decide order?

I am generating the script for views using SQL Server built-in feature (Task -> Generate script). I am creating separate file for each object (of view). I have say around 400 files (containing SQL script of all views) to be executed on another database and to do that automatically I have created BAT file which takes care of that.
There are views which are dependent on other views and due to that many views failed to execute. Is there any way by which we can set order of execution and get rid off the failure ?
Any pointers would be a great help.
Please let me know if you need more details.
Thanks
Jony
Could you try this query? You can execute the create scripts in order to "gen" (generation).
DECLARE #cnt int = 0, #index int;
DECLARE #viewNames table (number int, name varchar(max))
DECLARE #viewGen table (id uniqueidentifier, gen int, name varchar(max), parentId uniqueidentifier)
INSERT INTO #viewNames
SELECT ROW_NUMBER() OVER(ORDER BY object_Id), name FROM sys.views
SELECT #cnt = COUNT(*) FROM #viewNames
SET #index = #cnt;
WHILE ((SELECT COUNT(*) FROM #viewGen) < #cnt)
BEGIN
DECLARE #viewName varchar(200)
SELECT #viewName = name FROM #viewNames WHERE number = #index;
DECLARE #depCnt int = 0;
SELECT #depCnt = COUNT(*) FROM sys.dm_sql_referencing_entities ('dbo.' + #viewName, 'OBJECT')
IF (#depCnt = 0)
BEGIN
INSERT INTO #viewGen SELECT NEWID(), 0, name, null FROM #viewNames WHERE number = #index;
END
ELSE
BEGIN
IF EXISTS(SELECT * FROM sys.dm_sql_referencing_entities ('dbo.' + #viewName, 'OBJECT') AS r INNER JOIN #viewGen AS v ON r.referencing_entity_name = v.name)
BEGIN
DECLARE #parentId uniqueidentifier = NEWID();
INSERT INTO #viewGen SELECT #parentId, 0, name, null FROM #viewNames WHERE number = #index;
UPDATE v
SET v.gen = (v.gen + 1), parentId = #parentId
FROM #viewGen AS v
INNER JOIN sys.dm_sql_referencing_entities('dbo.' + #viewName, 'OBJECT') AS r ON r.referencing_entity_name = v.name
UPDATE #viewGen
SET gen = gen + 1
WHERE Id = parentId OR parentId IN (SELECT Id FROM #viewGen WHERE parentId = parentId)
END
END
SET #index = #index - 1
IF (#index < 0) BEGIN SET #index = #cnt; END
END
SELECT gen as [order], name FROM #viewGen ORDER BY gen
Expecting result:
order name
0 vw_Ancient
1 vw_Child1
1 vw_Child2
2 vw_GrandChild

Insert Data into Table if a condition is true

SQL statement Question
I have an instance where I need to insert one record multiple times in a DB table that contains a unique reference column. I'm declaring a variable and using a select statement to populate variable and then running an insert statement. The issue is that when grabbing unique value into variable it only grabs the last record in the reference table. I need for insert statement to insert at each point where a record is found.
DECLARE #ID INT;
DECLARE #RuleID INT;
SELECT #RuleID = RuleID from Rules where Rule_Name = 'VERSION_ID' and Field = 'TSH'
SELECT #ID = ID FROM CHANNELS WHERE SUBSTRING(CHANNEL_NAME,0,4) != 'HEL'
BEGIN
INSERT INTO Rule_Items
VALUES(#ID,#RuleID,0,'2.5.1','E','A',0,getdate())
END
If you want all combinations, you can use insert . . . select with a cross join:
INSERT INTO Rule_Items
select c.id, r.ruleid, 0, '2.5.1', 'E', 'A', 0, getdate()
from rules r cross join
channels c
where r.Rule_Name = 'VERSION_ID' and r.Field = 'TSH' and
SUBSTRING(c.CHANNEL_NAME, 0, 4) <> 'HEL';
I decided to use a cursor and create a temp table. Here is query that worked.
**--BEGIN TRAN
DECLARE #Channelid INT
DECLARE #RuleID INT
SELECT #RuleID = RuleID From Rules Where Rule_Name = 'VERSION_ID'
DECLARE GetChannelId CURSOR FOR SELECT Channelid FROM HL7_Channels WHERE Channel_Name not like 'MU2%' AND Channel_Description LIKE '%outbound%'
OPEN GetChannelId
FETCH GetChannelId INTO #Channelid
WHILE (##FETCH_STATUS = 0)
BEGIN
IF NOT EXISTS (Select 1 From Rule_Items Where ChannelId = #Channelid AND RuleID = #RuleID)
BEGIN
INSERT INTO Rule_Items VALUES (#Channelid,#RuleID,0,'2.5.1','E','A',0,getdate())
END
FETCH GetChannelId INTO #Channelid
CONTINUE
END
CLOSE GetChannelId
DEALLOCATE GetChannelId
--ROLLBACK TRAN
--COMMIT TRAN**

SQL: Retrieving Unique ID with Multiple Attributes

Ok, I had trouble describing this. I have:
material table (materialID, material, etc...)
ThicknessRange table (ThicknessRangeID, ThicknessRange)
MaterialThicknessRange table (MaterialID, ThicknessRangeID)
I am trying to retrieve all MaterialID's from the MaterialThicknessRange table that fit all required ThicknessRangeID's.
For example, any MaterialID with ThicknessRangeID 1 AND ThicknessRangeID 2, etc with a variable number of ThicknessRangeID's (selected from checkboxes by the user).
Thanks in advance.
Are you guaranteed to have only one entry in the MaterialThicknessRange table for a given Material/ThicknessRange combination?
SELECT MaterialID, COUNT(MaterialID) As NumMaterialThicknesses
FROM MaterialThicknessRange
WHERE ThicknessRangeID IN (1, 2)
GROUP BY MaterialID
HAVING COUNT(MaterialID) > 1
I'm using something like this
select MaterialID from MaterialThicknessRange MTR inner join
dbo.TransformCSVToTable('1,2,15') IDs on MTR.ThiknessRangeID = IDs.ID
where dbo.TransformCSVToTable is a user defined function to transform a csv string to a one column table. Bellow is one sample of such function
ALTER FUNCTION [dbo].[fn_IntegerParameterListFromString]
(
#IntegerParameterList varchar(max)
)
RETURNS #result TABLE (IntegerID int)
AS
begin
declare #temp table (IntegerID int)
declare #s varchar(max), #s1 varchar(10)
declare #len int
set #len =len(#IntegerParameterList)
set #s = #IntegerParameterList
if (right(#s,1)<>',') set #s = #s +','
while #s<>''
begin
set #s1 = substring(#s,1,charindex(',',#s)-1)
if (isnumeric(#s1)= 1)
insert #result (IntegerID) Values ( Cast(#s1 as int))
if (CHARINDEX(',',#s)>0)
begin
set #s = substring (#s, charindex(',',#s)+1, #Len)
end
else
begin
if isnumeric(#s) = 1
insert #result (IntegerID) Values ( Cast(#s as int))
set #s = ''
end
end
return
end