Cannot add an ORDER BY command to a stored procedure - sql

I'm converting a stored procedure in some software I'm maintaining from SQL Server SQL to Informix SQL, and problems are abundant.
Basically I'm converting each section line-by-line until I have the whole thing converted.
I have the following CREATE PROCEDURE:
CREATE PROCEDURE ifxdbase:dc_buildSP (WorkID INT, CompNo smallint)
CREATE TEMP TABLE Items
(
Code smallint,
Qty int,
Total int
);
INSERT INTO Items
SELECT
tblDetails.code,
tblDetails.quantity,
tblHead.quantity
FROM
tblHead
INNER JOIN tblDetails ON (tblDetails.compno = tblDetails.compno AND tblDetails.id_num = tblHead.id_num)
WHERE tblHead.compno = CompNo AND tblHead.id_num = WorkID;
--ORDER BY tblDetails.code;
DROP TABLE Items;
END PROCEDURE
As it stands, this works fine, but when I uncomment the line --ORDER BY tblDetails.seqno; (and remove the semicolon from the previous line) I get a "-201 A syntax error has occurred" error.
Basically tblHead is a series of order headers and tblDetails is a table of the details of each of those orders. Selecting and joining the data works fine, trying to order it fails.
Ordering should work with anything from the original SELECT, IIRC, so I can't see what could be going wrong, here...

As stated here:
..... not all clauses and options of
the SELECT statement are available for
you to use in a query within an
INSERT statement. The following SELECT
clauses and options are not supported
by Informix in an INSERT statement:
FIRST and INTO TEMP
ORDER BY and UNION
so ORDER BY is not supported in the INSERT command in Informix.
I don't have something to test right now, but you could try something like this, as a workaround:
INSERT INTO Items
SELECT code, dQuantity, hQuantity
FROM (
SELECT
tblDetails.code,
tblDetails.quantity dQuantity,
tblHead.quantity hQuantity
FROM
tblHead
INNER JOIN tblDetails ON (tblDetails.compno = tblDetails.compno AND tblDetails.id_num = tblHead.id_num)
WHERE tblHead.compno = CompNo AND tblHead.id_num = WorkID;
ORDER BY tblDetails.code
);

Related

Postgresql stored procedure temporary table variable is not a known variable

I've been doing research and trying things out a bunch of different ways, but with no success. I want to create a temporary table and then as I'm doing some searches, fill it up with stuff. I was able to do this with SQL Server by just declaring a table inside the procedure, but with Postgresql I've read I need to create a temporary table specifically.
My strategy started out with just
CREATE TEMP TABLE myTempTable
(
propOne bigint,
propTwo smallint,
createdAtUtc timestamp(6)
);
I even moved it to right after the "BEGIN". Down the file I get this error:
ERROR: "myTempTable" is not a known variable
LINE 77: SELECT * INTO myTempTable from myResult;
Next, I tried to create the temp table when I'm ready to fill it...
WITH some_updated_records AS
(
UPDATE dbTable
SET tablePropertyStatus = 3
WHERE tablePropertyDate < storedProcedurePropertyDate
RETURNING *
)
CREATE TEMP TABLE myTempTable as
(
SELECT *
FROM some_updated_records
);
I still get the same basic error above, but zero errors until it encounters the myTempTable variable.
I'm definitely not a SQL genius (perhaps, eventually, with your help), so there might be some other things I'm doing wrong. My whole task is to convert a SQL Server stored procedure to Postgresql.
What could I being doing wrong to make that temporary table variable un-declared? Is there a special way I need to declare it ahead of time? Am I making a mistake about how to create or declare a temporary table.
Another strategy could be to just keep saving records into a collection of types, forget the "temp table." Is there a way to do this in plpgsql?
UPDATE w/Examples
This version doesn't work. It stops at the create table.
CREATE OR REPLACE PROCEDURE MyTestProcedure(
p_Endpoint Varchar(256),
p_ContentType Varchar(200),
MaxInProcess int = NULL)
LANGUAGE plpgsql
AS $body$
DECLARE
v_UtcNow timestamp(6);
v_ExpiredProcessing timestamp(6);
BEGIN
SELECT CURRENT_TIMESTAMP into v_UtcNow at time zone 'utc';
WITH first_updated AS (UPDATE MyTable
SET Status = 1
WHERE UpdatedAtUtc < v_UtcNow
RETURNING Id, Status, UpdatedAtUtc)
CREATE TEMP TABLE IF NOT EXISTS statustable AS (SELECT Id, Status, UpdatedAtUtc FROM first_updated)
WITH m_result AS (UPDATE MyTable
SET Status = 3,
WHERE ExpirationDateTimeUtc < v_UtcNow
RETURNING Id, Status, UpdatedAtUtc)
INSERT INTO statustable from m_result;
DROP TABLE statustable;
END;
$body$
This errors out at the table creation.
INE 22: CREATE TEMP TABLE statustable as...
The other example would be something similar to creating the table first and then inserting into it. That's probably where I messed up. Working solution will be added in a minute, if someone doesn't add it in first.
You can use a CTE, but put the CTE within the parentheses for the table creation.
CREATE TEMPORARY TABLE myTempTable AS (
WITH cte_updated_records AS (
UPDATE dbTable
SET tablePropertyStatus = 3
WHERE tablePropertyDate < storedProcedurePropertyDate
RETURNING *
)
SELECT * FROM cte_updated_records
);
https://www.postgresql.org/docs/14/plpgsql-statements.html#PLPGSQL-STATEMENTS-ASSIGNMENT1
Please refer the Tip section:
Tip Note that this interpretation of SELECT with INTO is quite
different from PostgreSQL's regular SELECT INTO command, wherein the
INTO target is a newly created table. If you want to create a table
from a SELECT result inside a PL/pgSQL function, use the syntax CREATE
TABLE ... AS SELECT.
based on this then you can do
CREATE TEMP TABLE statustable AS (here is your query clause)
Maybe you can do update later.
Another Point is as per manual, seems you cannot do CREATE Table by using CTE.
Each auxiliary statement in a WITH clause can be a SELECT, INSERT,
UPDATE, or DELETE; and the WITH clause itself is attached to a primary
statement that can also be a SELECT, INSERT, UPDATE, or DELETE.
https://www.postgresql.org/docs/current/queries-with.html
LukStorms's answer is pretty neat. But serval steps maybe more readable?
When you're debugging, things can get a little crazy. What happens often, I find, is I try one good solution, but I don't know how to implement it quite right, so the following works. I think I was forgetting the select in the INSERT INTO's.
CREATE OR REPLACE PROCEDURE MyTestProcedure(
p_Endpoint Varchar(256),
p_ContentType Varchar(200),
MaxInProcess int = NULL)
LANGUAGE plpgsql
AS $body$
DECLARE
v_UtcNow timestamp(6);
v_ExpiredProcessing timestamp(6);
BEGIN
SELECT CURRENT_TIMESTAMP into v_UtcNow at time zone 'utc';
CREATE TEMP TABLE status_table(
Id bigint,
Status smallint,
CreatedAtUtc timestamp(6));
WITH first_updated AS (UPDATE MyTable
SET Status = 1
WHERE UpdatedAtUtc < v_UtcNow
RETURNING Id, Status, UpdatedAtUtc)
INSERT INTO status_table
SELECT Id, Status, UpdatedAtUtc
FROM first_updated;
WITH m_result AS (UPDATE MyTable
SET Status = 3
WHERE ExpirationDateTimeUtc < v_UtcNow
RETURNING Id, Status, UpdatedAtUtc)
INSERT INTO status_table
select Id, Status, UpdatedAtUtc
from m_result;
DROP TABLE status_table;
END;
$body$

Reuse results of SELECT query inside a stored procedure

This is probably a very simple question, but my attempts to search for an answer are thwarted by Google finding answers showing how to reuse a query by making a stored procedure instead. I want to reuse the results of a query inside a stored procedure.
Here's a cut-down example where I've chopped out NOCOUNT, XACT_ABORT, TRANSACTION, TRY, and much of the logic.
CREATE PROCEDURE Do_Something
#userId UNIQUEIDENTIFIER
AS
BEGIN
DELETE FROM LikedItems
WHERE likedItemId IN
(
SELECT Items.id FROM Items
WHERE Items.userId = #userId
)
DELETE FROM FollowedItems
WHERE followedItemId IN
(
SELECT Items.id FROM Items
WHERE Items.userId = #userId
)
END
What is the syntax to reuse the results of the duplicated nested SELECT rather than doing it twice?
You can INSERT result of the SELECT into a temporary table or table variable, but it doesn't automatically mean that the overall performance would be better. You need to measure it.
Temp Table
CREATE PROCEDURE Do_Something
#userId UNIQUEIDENTIFIER
AS
BEGIN
CREATE TABLE #Temp(id int);
INSERT INTO #Temp(id)
SELECT Items.id
FROM Items
WHERE Items.userId = #userId;
DELETE FROM LikedItems
WHERE likedItemId IN
(
SELECT id FROM #Temp
)
DELETE FROM FollowedItems
WHERE followedItemId IN
(
SELECT id FROM #Temp
)
DROP TABLE #Temp;
END
Table variable
CREATE PROCEDURE Do_Something
#userId UNIQUEIDENTIFIER
AS
BEGIN
DECLARE #Temp TABLE(id int);
INSERT INTO #Temp(id)
SELECT Items.id
FROM Items
WHERE Items.userId = #userId;
DELETE FROM LikedItems
WHERE likedItemId IN
(
SELECT id FROM #Temp
)
DELETE FROM FollowedItems
WHERE followedItemId IN
(
SELECT id FROM #Temp
)
END
You can declare a table variable to store the results of the select and then simply query that.
CREATE PROCEDURE Do_Something
#userId UNIQUEIDENTIFIER
AS
BEGIN
DECLARE #TempItems TABLE (id int)
INSERT INTO #TempItems
SELECT Items.id FROM Items
WHERE Items.userId = #userId
DELETE FROM LikedItems
WHERE likedItemId IN
(
SELECT id FROM #TempItems
)
DELETE FROM FollowedItems
WHERE followedItemId IN
(
SELECT id FROM #TempItems
)
END
If the subquery is fast and simple - no need to change anything. Item's data is in the cache (if it was not) after the first query, locks are obtained. If the subquery is slow and complicated - store it into a table variable and reuse by the same subquery as listed in the question.
If your question is not related to performance and you are beware of copy-paste: there is no copy-paste. There is the same logic, similar structure and references - yes, you will have almost the same query source code.
In general, it is not the same. Some rows could be deleted from or inserted into Items table after the first query unless your are running under SERIALIZABLE isolation level. Many different things could happen during first delete, between first and second delete statements. Each delete statement also requires it's own execution plan - thus all the information about tables affected and joins must be provided to SERVER anyway. You need to filter by the same source again - yes, you provide subquery with the same source again. There is no "twice" or "reuse" of a partial code. Data collected by a complicated query - yes, it can be reused (without running the same complicated query - by simple querying from prepared source) via temp tables/table variables as mentioned before.

I am trying to run a query based on the results from a stored procedure

First, here is the code for sp_GetWorkQByUserName:
ALTER PROCEDURE [dbo].[sp_GetWorkQByUserName]
( #UserName varchar(50),
#StartDate datetime,
#EndDate datetime )
AS
BEGIN
SET NOCOUNT ON;
SELECT DISTINCT SpotId FROM tblSpotCount WHERE StoreNum = EXECUTE sp_GetUserLocationCodes(#UserName)
ORDER BY SpotDt ASC
END
I know my SELECT DISTINCT statement is wrong, but I wrote it like that to help show what I'm trying to do. I want to run this stored procedure based on the results from the sp_GetUserLocationCodes with a parameter of #UserName.
From what I can tell, my problem lies in how I'm calling sp_GetUserLocationCodes.
Question: how can I run a SELECT DISTINCT query on tblSpotCount.SpotId based on the results from the sp_GetUserLocationCodes stored procedure?
You cannot use a stored procedure directly in a query. You can, however, insert the results of a stored procedure into a temporary table and use that in your query:
CREATE TABLE #storeLocations
(
-- appropriate column names and data types go here
)
INSERT INTO #storeLocations (put column list here)
EXECUTE sp_GetUserLocationCodes(#UserName)
SELECT DISTINCT SpotId
FROM tblSpotCount
WHERE EXISTS (SELECT 1
FROM #storeLocations
WHERE #storeLocations.StoreNum = tblSpotCount.StoreNum)
ORDER BY SpotDt ASC
DROP TABLE #storeLocations

Dynamically Select from different DB's based on input to sproc

I'm trying to alter a stored procedure in our DB from a hard-coded select from 1 specific DB to be able to select from any of our DB's based on an id that's passed into the sproc. Here's the stub of what the sproc is doing for us:
ALTER PROCEDURE [dbo].[GetByAdId]
(
#AdId int,
#UserCompanyId int
)
AS
SET NOCOUNT ON
SELECT
[User].[UserId],
UserMappings.IsActive,
IsAccountOwner = ( SELECT Count(*) FROM DB1_SetUp.dbo.ad Adv WHERE Adv.AdID = UserMappings.AdID AND Adv.PrimaryAccountOwnerID = [User].[UserId] )
FROM
[User] INNER JOIN UserMappings ON
(
UserMappings.UserID = [User].UserID
AND UserMappings.AdID = #AdId
AND UserMappings.UserCompanyId = #UserCompanyId
)
Basically the "IsAccountOwner" variable is hardcoded to select from DB1_SetUp every time, but we have a number of SetUp db's for different groups, so like DB2_SetUp, DB3_SetUp, etc. The UserCompanyId variable being passed into the sproc functions like a group Id and can be used to point to the particular SetUp DB I want it to select from, but I'm not sure how to do this. I basically wanted something on the ilk of:
SELECT * FROM (
CASE #UserCompanyId
WHEN 3 THEN 'DB3_SetUp'
WHEN 4 THEN 'DB4_SetUp'
)
Is there a clean way to do this, or will I have to setup this sproc on each group DB and call the specific one over on each DB?
I've done this in the past by dynamically building the SQL I wanted to execute (based on parameters passed in) and then executing the SQL using sp_executesql
see: http://msdn.microsoft.com/en-us/library/ms188001.aspx

How can one iterate over stored procedure results from within another stored procedure....without cursors?

I'm not sure if this is something I should do in T-SQL or not, and I'm pretty sure using the word 'iterate' was wrong in this context, since you should never iterate anything in sql. It should be a set based operation, correct? Anyway, here's the scenario:
I have a stored proc that returns many uniqueidentifiers (single column results). These ids are the primary keys of records in a another table. I need to set a flag on all the corresponding records in that table.
How do I do this without the use of cursors? Should be an easy one for you sql gurus!
This may not be the most efficient, but I would create a temp table to hold the results of the stored proc and then use that in a join against the target table. For example:
CREATE TABLE #t (uniqueid int)
INSERT INTO #t EXEC p_YourStoredProc
UPDATE TargetTable
SET a.FlagColumn = 1
FROM TargetTable a JOIN #t b
ON a.uniqueid = b.uniqueid
DROP TABLE #t
You could also change your stored proc to a user-defined function that returns a table with your uniqueidentifiers. You can joing directly to the UDF and treat it like a table which avoids having to create the extra temp table explicitly. Also, you can pass parameters into the function as you're calling it, making this a very flexible solution.
CREATE FUNCTION dbo.udfGetUniqueIDs
()
RETURNS TABLE
AS
RETURN
(
SELECT uniqueid FROM dbo.SomeWhere
)
GO
UPDATE dbo.TargetTable
SET a.FlagColumn = 1
FROM dbo.TargetTable a INNER JOIN dbo.udfGetUniqueIDs() b
ON a.uniqueid = b.uniqueid
Edit:
This will work on SQL Server 2000 and up...
Insert the results of the stored proc into a temporary table and join this to the table you want to update:
INSERT INTO #WorkTable
EXEC usp_WorkResults
UPDATE DataTable
SET Flag = Whatever
FROM DataTable
INNER JOIN #WorkTable
ON DataTable.Ket = #WorkTable.Key
If you upgrade to SQL 2008 then you can pass table parameters I believe. Otherwise, you're stuck with a global temporary table or creating a permanent table that includes a column for some sort of process ID to identify which call to the stored procedure is relevant.
How much room do you have in changing the stored procedure that generates the IDs? You could add code in there to handle it or have a parameter that lets you optionally flag the rows when it is called.
Use temporary tables or a table variable (you are using SS2005).
Although, that's not nest-able - if a stored proc uses that method then you can't dumpt that output into a temp table.
An ugly solution would be to have your procedure return the "next" id each time it is called by using the other table (or some flag on the existing table) to filter out the rows that it has already returned
You can use a temp table or table variable with an additional column:
DECLARE #MyTable TABLE (
Column1 uniqueidentifer,
...,
Checked bit
)
INSERT INTO #MyTable
SELECT [...], 0 FROM MyTable WHERE [...]
DECLARE #Continue bit
SET #Continue = 1
WHILE (#Continue)
BEGIN
SELECT #var1 = Column1,
#var2 = Column2,
...
FROM #MyTable
WHERE Checked = 1
IF #var1 IS NULL
SET #Continue = 0
ELSE
BEGIN
...
UPDATE #MyTable SET Checked = 1 WHERE Column1 = #var1
END
END
Edit: Actually, in your situation a join will be better; the code above is a cursorless iteration, which is overkill for your situation.