SELECT ... INTO a variable within a postgres CTE - postgresql

I want to do this inside a plpgsql function
WITH set1 AS (
select *
from table1
where ... -- reduce table1 to the small working set once for multiple reuse
), query_only_for_select_into AS (
select id
into my_variable_declared_earlier
from set1
where foo = 'bar'
)
select my_variable_declared_earlier as my_bar
, *
from set1
where foo <> 'bar'
but Postgres throws the error
ERROR: SELECT ... INTO is not allowed here
I'm guessing it's because the select ... into is in the CTE. But I can't find anything in the documentation or on the web about it. Maybe I just messed up the select ... into syntax?

SQL has no variables - they are part of a procedural language (e.g. PL/pgSQL), not the query language.
But I don't see the reason why you need one:
WITH set1 AS (
select *
from table1
where ... -- reduce table1 to the small working set once for multiple reuse
), query_only_for_select_into AS (
select id as my_variable_declared_earlier
from set1
where foo = 'bar'
)
select qs.my_variable_declared_earlier as my_bar,
*
from set1
join query_only_for_select_into qs on ...
where foo <> 'bar'
If you are certain that query_only_for_select_into only returns a single row, you can use:
select qs.my_variable_declared_earlier as my_bar,
*
from set1
cross join query_only_for_select_into qs
where foo <> 'bar'

SELECT ... INTO variable is PL/pgSQL syntax, and you can only use it with the top level SELECT (the one that returns results. Think of it like that: PL/pgSQL runs an SQL statement and stores the result somewhere.
But you don't need that: simply include query_only_for_select_into in the FROM clause of the main query, then you can access its columns there.

Related

postgresql WITH statement doesn't find auxiliary statements

I have this query:
WITH words_not AS (
SELECT keywords.id
FROM keywords
WHERE keyword = any(array['writing'])
),actes_not AS (
SELECT actes_keywords.acte_id
FROM actes_keywords
WHERE actes_keywords.keyword_id IN (words_not)
)
SELECT actes.id,
actes.acte_date
FROM actes
WHERE actes.id <> all(actes_not);
This returns the following error:
ERROR: column "words_no" does not exist
LINE 1: ...ctes_keywords WHERE actes_keywords.keyword_id IN (mots_non))...
Each auxiliary statement in the WITH query is good (tested) and I thought I was staying pretty close to the manual: https://www.postgresql.org/docs/current/static/queries-with.html
I don't see why the auxiliary statement in the WITH query is not recognised.
You can't use a table reference in an IN (..) clause. You need a sub-query:
WITH words_not AS (
SELECT keywords.id
FROM keywords
WHERE keyword = any(array['writing'])
), actes_not AS (
SELECT actes_keywords.acte_id
FROM actes_keywords
WHERE actes_keywords.keyword_id IN (select id from words_not) --<< HERE
)
SELECT actes.id,
actes.acte_date
FROM actes
WHERE actes.id <> all(select id from actes_not); --<< HERE

SELECT clause in FOR LOOP control using plpgsql

I try to make a script as to output all the foos that are used by only one user, if a foo is used by more than one user, it shouldn't be outputed.
here's my tables
foos (id, value)
users (id, name)
used (foo_id, user_id)
and my not working script
FUNCTION output_unshared_foos ()
RETURNS foos AS
$a$
DECLARE
foocounts RECORD;
BEGIN
SELECT u.foo_id, count(*)
INTO foocounts -- store in the local variable
FROM used u
GROUP BY u.foo_id;
FOR f IN SELECT * FROM foos
LOOP
IF (SELECT fc.count < 2 FROM foocounts fc WHERE fc.foo_id = f.id) THEN
RETURN NEXT f;
END IF;
END LOOP;
END
$a$ language plpgsql;
doesn't seem to work, every rows are returned and the conditional control seems to be always true.
Your first problem is that you can't store the result of a query that returns more than one row into a single variable (the SELECT u.foo_id, count(*) INTO ... part). I'm surprised you don't get a runtime error when you call your function.
Your function also doesn't compile because the record f is not declared and a functioned defined as returns foos can't use return next
But your approach is wrong (even if it worked). Doing row-by-row processing is almost always the wrong choice in SQL. SQL and relational databases are meant to handle sets, not single rows.
Your problem can be solved with a single query:
select foo_id
from used
group by foo_id
having count(distinct user_id) = 1
will return all foo ids that are used by exactly one user.
If you need the additional information from the foos table, you can join the above query to the foos table:
select f.*
from foos f
join (
select foo_id
from used
group by foo_id
having count(distinct user_id) = 1
) u on f.id = u.foo_id

TSQL - Passing fields from table into stored procedure then storing result in temp table

I'm working with a client who has a stored procedure with about a dozen parameters.
I need to get the parameter values from tables in the database, then feed these into the stored procedure to get a number value. I then need to join this value to a SELECT statement.
I know that I have to build a temp table in order to join the SP results with my select statement, but this is all new to me and could use some help. Mostly focusing on how to feed field values into the SP. I would also like the Temp table to contain a couple of the parameters as fields so I can join it to my select statement.
any and all help is appreciated.
Thank You
You can capture the parameter values in declared variables. Something like:
DECLARE #Parm1 int, #Parm2 varchar(50) -- Use appropriate names and datatypes
SELECT #Parm1 = Parm1ColumnName, #Parm2=Parm2ColumnName
FROM TableWithParmValues
-- Include a WHERE condition if appropriate
DECLARE #ProcOutput TABLE(outputvalue int) -- use appropriate names and datatypes to match output
INSERT #ProcOuptut
EXECUTE MyProc #ProcParm1 = #Parm1, #ProcParm2 = #Parm2 -- Use appropriate names
Then use the #ProcOutput temp table, and parameter variables as you need with your SELECT.
This is a comment that is better formatted as an answer.
You don't need to create a temporary table, or table variable, to be able to join a numeric result with other data. The following demonstrates various curiosities using SELECTs without explicitly creating any tables:
declare #Footy as VarChar(16) = 'soccer'
select * from (
select 'a' as Thing, 42 as Thingosity
union all
select *
from ( values ( 'b', 2 ), ( 'c', 3 ), ( #Footy, Len( #Footy ) ) ) as Placeholder ( Thing, Thingosity )
) as Ethel cross join
( select 42 as TheAnswer ) as Fred

T-SQL Why can I reffer only once to temporary object?

with tmp_rows as
(
select * from [dbo].[customer]
)
select * from tmp_rows;
select count(*) from tmp_rows;
I can't get the count of the tmp_rows because I get the error: Invalid object name 'tmp_rows'
If I comment the "select *" query everything is OK
I need to select all rows and then get their count, how to do that ?
with tmp_rows as
(
select * from [dbo].[customer]
)
select * from tmp_rows;
select ##rowcount;
By declaring you statement in using with you are declaring a CTE - further information on CTE's can be found here
A temporary object created using the with keyword can only be used once. You can create a temporary table if you want to use it more than once:
select *
into #tmp_tows
from dbo.customer
select * from #tmp_rows
select count(*) from #tmp_rows
drop table #tmp_rows
This works even if you want to do something different with the result twice, for example getting the count before the result.
The CTE starting with WITH ends with the semicolon ; .
But you can have more than 1 CTE in a WITH statement:
with tmp_rows as
(
select * from [dbo].customer
),
count_rows as
(
select COUNT(*) count_rows from tmp_rows
)
select * from count_rows, tmp_rows;
tmp_rows is a Common Table Expression (CTE), and CTEs are scoped at the statement level:
-- 1st statement, works fine.
with tmp_rows as (select * from [dbo].[customer])
select * from tmp_rows;
-- 2nd statement, returns an error, since tmp_rows is out of scope.
select count(*) from tmp_rows;
By the time your second statement executes, tmp_rows is already out of scope.
Be aware that CTEs are like locally-scoped views, not tables. The result set is never materialized. If you need to materialize a result set, use a local temporary table or table variable instead.

tsql - using internal stored procedure as parameter is where clause

I'm trying to build a stored procedure that makes use of another stored procedure. Taking its result and using it as part of its where clause, from some reason I receive an error:
Invalid object name 'dbo.GetSuitableCategories'.
Here is a copy of the code:
select distinct top 6 * from
(
SELECT TOP 100 *
FROM [dbo].[products] products
where products.categoryId in
(select top 10 categories.categoryid from
[dbo].[GetSuitableCategories]
(
-- #Age
-- ,#Sex
-- ,#Event
1,
1,
1
) categories
ORDER BY NEWID()
)
--and products.Price <=#priceRange
ORDER BY NEWID()
)as d
union
select * from
(
select TOP 1 * FROM [dbo].[products] competingproducts
where competingproducts.categoryId =-2
--and competingproducts.Price <=#priceRange
ORDER BY NEWID()
) as d
and here is [dbo].[GetSuitableCategories] :
if (#gender =0)
begin
select * from categoryTable categories
where categories.gender =3
end
else
begin
select * from categoryTable categories
where categories.gender = #gender
or categories.gender =3
end
I would use an inline table valued user defined function. Or simply code it inline is no re-use is required
CREATE dbo.GetSuitableCategories
(
--parameters
)
RETURNS TABLE
AS
RETURN (
select * from categoryTable categories
where categories.gender IN (3, #gender)
)
Some points though:
I assume categoryTable has no gender = 0
Do you have 3 genders in your categoryTable? :-)
Why do pass in 3 parameters but only use 1? See below please
Does #sex map to #gender?
If you have extra processing on the 3 parameters, then you'll need a multi statement table valued functions but beware these can be slow
You can't use the results of a stored procedure directly in a select statement
You'll either have to output the results into a temp table, or make the sproc into a table valued function to do what you doing.
I think this is valid, but I'm doing this from memory
create table #tmp (blah, blah)
Insert into #tmp
exec dbo.sprocName