Using variables in simple PostgresSQL queries - postgresql

I have to perfrom a lot of queries where the same value is being reused. I thought of something like:
varName = 'value';
select * from sometable t where
t.field1 = varName
or t.field2 = varName;
How can this be done with PostgreSQL 12?
I tried a lot of stuff I found, but nothing seems to work.

I found a solution meanwhile
with varName as (select 'value'::text)
select * from sometable t where
t.field1 = (select * from varName)
or t.field2 = (select * from varName);

Alternatively you can use VALUES inside of your CTE. This will enable you to have multiple values in the same "variable".
Data Sample:
CREATE TABLE t (c1 int, c2 text);
INSERT INTO t VALUES (42,'foo'),(1,'xpto');
Query
WITH j (var) AS (VALUES ('foo'),('bar'))
SELECT t.c1,j.var FROM t
JOIN j ON j.var = t.c2;
c1 | var
----+-----
42 | foo

Related

Postgresql - reference selected values inside subqueries

you can do something like below in MySQL easily:
Select
#date := `Date`,
(Select sum(High) From tsdata where `Date` = #date) As MySum
From sometable Where type = 'A'
How can I do something similar in postgres?
Many thanks!

SQL Server : Split string to row

How to turn data from below:
CODE COMBINATION USER
1111.111.11.0 KEN; JIMMY
666.778.0.99 KEN
888.66.77.99 LIM(JIM); JIMMY
To
CODE COMBINATION USER
1111.111.11.0 KEN
1111.111.11.0 JIMMY
666.778.0.99 KEN
888.66.77.99 LIM(JIM)
888.66.77.99 JIMMY
I know in SQL Server 2016 this can be done by split string function, but my production is SQL Server 2014.
With this TVF, you can supply the string to be split and delimiter. Furthermore, you get the sequence number which can be very useful for secondary processing.
Select [CODE COMBINATION]
,[USER] = B.RetVal
From YourTable A
Cross Apply [dbo].[udf-Str-Parse](A.[USER],';') B
Returns
The Parse UDF
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
--Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
Now, another option is the Parse-Row UDF. Notice we return the parsed string in one row. Currently 9 positions, but it is easy to expand or contract.
Select [CODE COMBINATION]
,B.*
From YourTable A
Cross Apply [dbo].[udf-Str-Parse-Row](A.[USER],';') B
Returns
The Parse Row UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-Row] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos1 = xDim.value('/x[1]','varchar(max)')
,Pos2 = xDim.value('/x[2]','varchar(max)')
,Pos3 = xDim.value('/x[3]','varchar(max)')
,Pos4 = xDim.value('/x[4]','varchar(max)')
,Pos5 = xDim.value('/x[5]','varchar(max)')
,Pos6 = xDim.value('/x[6]','varchar(max)')
,Pos7 = xDim.value('/x[7]','varchar(max)')
,Pos8 = xDim.value('/x[8]','varchar(max)')
,Pos9 = xDim.value('/x[9]','varchar(max)')
From (Select Cast('<x>' + Replace(#String,#Delimiter,'</x><x>')+'</x>' as XML) as xDim) A
)
--Select * from [dbo].[udf-Str-Parse-Row]('Dog,Cat,House,Car',',')
--Select * from [dbo].[udf-Str-Parse-Row]('John Cappelletti',' ')
You need to use a UDF for splitting it on each row
CREATE FUNCTION [DBO].[FN_SPLIT_STR_TO_COL] (#T AS VARCHAR(4000) )
RETURNS
#RESULT TABLE(VALUE VARCHAR(250))
AS
BEGIN
SET #T= #T+';'
;WITH MYCTE(START,[END]) AS(
SELECT 1 AS START,CHARINDEX(';',#T,1) AS [END]
UNION ALL
SELECT [END]+1 AS START,CHARINDEX(';',#T,[END]+1)AS [END]
FROM MYCTE WHERE [END]<LEN(#T)
)
INSERT INTO #RESULT
SELECT SUBSTRING(#T,START,[END]-START) NAME FROM MYCTE;
RETURN
END
Now query on your table by calling above function with CROSS APPLY
SELECT [CodeCombination],FN_RS.VALUE FROM TABLE1
CROSS APPLY
(SELECT * FROM [DBO].[FN_SPLIT_STR_TO_COL] (User))
AS FN_RS
If your [USER] column only has one semicolon you don't need a "split string" function at all; you could use CROSS APPLY like this:
-- Your Sample data
DECLARE #table TABLE (CODE_COMBINATION varchar(30), [USER] varchar(100));
INSERT #table
VALUES ('1111.111.11.0', 'KEN; JIMMY'), ('666.778.0.99', 'XKEN'),
('888.66.77.99','LIM(JIM); JIMMY');
-- Solution using only CROSS APPLY
SELECT CODE_COMBINATION, [USER] = LTRIM(s.s)
FROM #table t
CROSS APPLY (VALUES (CHARINDEX(';',t.[USER]))) d(d)
CROSS APPLY
(
SELECT SUBSTRING(t.[USER], 1, ISNULL(NULLIF(d.d,0),1001)-1)
UNION ALL
SELECT SUBSTRING(t.[USER], d.d+1, 1000)
WHERE d.d > 0
) s(s);
If you do need a pre SQL Server 2016 "split string" function I would strongly suggest using Jeff Moden's DelimitedSplit8k or Eirikur Eiriksson's DelimitedSplit8K_LEAD. Both of these will outperform an XML-based or recursice CTE "split string" function.

Postgres Select from a Table Based On Query Result

I have two tables with identical columns, in an identical order. I have a desire to join across one of the two tables, depending on a subquery condition. For example, assume I have the following schema:
CREATE TABLE b (
bid SERIAL PRIMARY KEY,
cid INT NOT NULL
);
CREATE TABLE a1 (
aid SERIAL PRIMARY KEY,
bid INT NOT NULL REFERENCES b
);
CREATE TABLE a2 (
aid SERIAL PRIMARY KEY,
bid INT NOT NULL REFERENCES b
);
I would like a query, that performs a join across either a1 or a2 based on some condition. Something like:
WITH z AS (
SELECT cid, someCondition FROM someTable
)
SELECT *
FROM CASE z.someCondition THEN a1 ELSE a2 END
JOIN b USING (bid)
WHERE cid = (SELECT cid FROM z);
However, the above doesn't work. Is there some way to conditionally join across a1 or a2, depending on some boolean condition stored in table z?
If the conditions are exclusive (I expect they are): just do both queries and UNION ALL them, with the smart union construct:
WITH z AS (
SELECT cid
, (cid %3) AS some_condition -- Fake ...
FROM b
)
SELECT *
FROM a1
JOIN b USING (bid)
WHERE EXISTS( SELECT * FROM z
WHERE some_condition = 1 AND cid = b.cid )
UNION ALL
SELECT *
FROM a2
JOIN b USING (bid)
WHERE EXISTS( SELECT * FROM z
WHERE some_condition = 2 AND cid = b.cid )
;
A somewhat different syntax to do the same:
WITH z AS (
SELECT cid
, (cid %3) AS some_condition
FROM b
)
SELECT *
FROM a1
JOIN b ON a1.bid = b.bid
AND EXISTS( SELECT * FROM z
WHERE some_condition = 1 AND cid = b.cid )
UNION ALL
SELECT *
FROM a2
JOIN b ON a2.bid = b.bid
AND EXISTS( SELECT * FROM z
WHERE some_condition = 2 AND cid = b.cid )
;
SQL syntax does not allow conditional joins.
Probably the simplest way to achieve a similar effect is to use a dynamic query in a plpgsql function, which may look like this:
create function conditional_select(acid int, some_condition boolean)
returns table (aid int, bid int, cid int)
language plpgsql as $$
declare
tname text;
begin
if some_condition then tname = 'a1';
else tname = 'a2';
end if;
return query execute format ($fmt$
select a.aid, b.bid, b.cid
from %s a
join b using(bid)
where cid = %s;
$fmt$, tname, acid);
end $$;
select * from conditional_select(1, true)
If, like in your example, you have only a few columns that you want to output, you can use the CASE statement for every column:
SELECT CASE z.someCondition THEN a1.aid ELSE a2.aid END AS aid,
CASE z.someCondition THEN a1.bid ELSE a2.bid END AS bid
FROM b
JOIN a1 ON a1.bid = b.bid
JOIN a2 ON a2.bid = b.bid
JOIN someTable z USING (cid);
Depending on the size of tables a1 and a2 and how many columns you have to output, this may or my not be faster than Klin's solution with a function, which is inherently slower than plain SQL and even more so because of the dynamic query. Given that z.someCondition is a boolean value already, the CASE evaluation will be very fast. Small tables + few columns = this solution; large tables + many columns = Klin's solution.

Postgres ANY operator with array selected in a subquery

Can someone explain to me why the 4th select works, but the first 3 do not? (I'm on PostgreSQL 9.3.4 if it matters.)
drop table if exists temp_a;
create temp table temp_a as
(
select array[10,20] as arr
);
select 10 = any(select arr from temp_a); -- ERROR: operator does not exist: integer = integer[]
select 10 = any(select arr::integer[] from temp_a); -- ERROR: operator does not exist: integer = integer[]
select 10 = any((select arr from temp_a)); -- ERROR: operator does not exist: integer = integer[]
select 10 = any((select arr from temp_a)::integer[]); -- works
Here's a sqlfiddle: http://sqlfiddle.com/#!15/56a09/2
You might be expecting an aggregate. Per the documentation:
Note: Boolean aggregates bool_and and bool_or correspond to standard SQL aggregates every and any or some. As for any and some, it seems that there is an ambiguity built into the standard syntax:
SELECT b1 = ANY((SELECT b2 FROM t2 ...)) FROM t1 ...;
Here ANY can be considered either as introducing a subquery, or as being an aggregate function, if the subquery returns one row with a Boolean value. Thus the standard name cannot be given to these aggregates.
In Postgres, the any operator exists for subqueries and for arrays.
The first three queries return a set of values of type int[] and you're comparing them to an int. Can't work.
The last query is returning an int[] array but it's only working because you're returning a single element.
Exhibit A; this works:
select (select i from (values (array[1])) rows(i))::int[];
But this doesn't:
select (select i from (values (array[1]), (array[2])) rows(i))::int[];
This works as a result (equivalent to your fourth query):
select 1 = any((select i from (values (array[1])) rows(i))::int[]);
But this doesn't (equivalent to your fourth query returning multiple rows):
select 1 = any((select i from (values (array[1]), (array[2])) rows(i))::int[]);
These should also work, btw:
select 1 = any(
select unnest(arr) from temp_a
);
select 1 = any(
select unnest(i)
from (values (array[1]), (array[2])) rows(i)
);
Also note the array(select ...)) construct as an aside, since it's occasionally handy:
select 1 = any(array(
select i
from (values (1), (2)) rows(i)
));
select 1 = any(
select i
from (values (1), (2)) rows(i)
);

How to filter records for all rows?

I am designing a SQL query to extract all records from a given table. But the trick here is that this logic is based on a numeric database field. So there are 4 choices: 0,1,2,3. If user selects 0,1, or 2, then my query returns rows with the specified value. But if they choose 3, it should return all of the rows. How do I do this in SQL? I know if this was a string, I could do something like:
WHERE = CASE WHEN = 3 THEN '%' ELSE END
But in this case, is an integer. Sounds relatively simple but I'm getting errors.
Try this:
SELECT *
FROM <YOUR_TABLE>
WHERE
(
<YOUR_COLUMN> = #InputValue OR
3 = #InputValue
)
Where #InputValue is the name of parameter sent to the query.
The simplest way is to do this:
select MyColumn
from MyTable
where ( MyValue = #MyParameter or #MyParameter = 3)
If your interested in better optimization, then you can do this, but it is less maintainable:
if (#MyParameter = 3)
select MyColumn
from MyTable
else
select MyColumn
from MyTable
where MyValue = #MyParameter
If I were forced to implement this functionality, then I would probably do this, just to make things clear:
declare #AllRecords nchar(1)
if (#MyParameter = 3)
set #AllRecords = N'Y'
else
set #AllRecords = N'N'
select MyColumn
from MyTable
where (MyValue = #MyParameter or #AllRecords = N'Y')
Hopefully, I won't ever have to implement a system that mixes flags and data value in this way.
UPDATED
Here is a version that should work with your expanded requirements (this requires one of the newer versions of SQL Server, I think):
declare #SelectedLevels table (LevelId int not null primary key)
if #LevelId = 3
insert into #SelectedLevels (LevelId) values (1), (2)
else if #LevelId = 5
insert into #SelectedLevels (LevelId) values (0), (1), (2)
else
insert into #SelectedLevels (LevelId) values (#LevelId)
select mt.MyColumn
from MyTable mt
inner join #SelectedLevels sl on sl.LevelId = MyTable.LevelId
if #Param = 3
begin
select *
from #T
end
else
if #Param = 2
begin
select *
from #T
where id in (0,1)
end
else
begin
select *
from #T
where id = #Param
end