Iterate through "linked list" in one SQL query? - sql

I have a table that looks basically like this:
id | redirectid | data
where the redirectid is an id to another row. Basically if a row is selected, and it has a redirectid, then the redirectid data should be used in it's place. There may be multiple redirects until redirectid is NULL. Essentially these redirects form a linked list in the table. What I'd like to know is, given an id, is it possible to set up a sql query that will iterate through all the possible redirects and return the id at the end of the "list"?
This is using Postgresql 8.3 and I'd like to do everything in on sql query if possible (rather than iterate in my code).

Does postgresql support recursive queries that use WITH clauses? If so, something like this might work. (If you want a tested answer, provide some CREATE TABLE and INSERT statements in your question, along with the results you need for the sample data in the INSERTs.)
with Links(id,link,data) as (
select
id, redirectid, data
from T
where redirectid is null
union all
select
id, redirectid, null
from T
where redirectid is not null
union all
select
Links.id,
T.redirectid,
case when T.redirectid is null then T.data else null end
from T
join Links
on Links.link = T.id
)
select id, data
from Links
where data is not null;
Additional remarks:
:( You can implement the recursion yourself based on the WITH expression. I don't know postgresql syntax for sequential programming, so this is a bit pseudo:
Insert the result of this query into a new table called Links:
select
id, redirectid as link, data, 0 as depth
from T
where redirectid is null
union all
select
id, redirectid, null, 0
from T
where redirectid is not null
Also declare an integer ::depth and initialize it to zero. Then repeat the following until it no longer adds rows to Links. Links will then contain your result.
increment ::depth;
insert into Links
select
Links.id,
T.redirectid,
case when T.redirectid is null then T.data else null end,
depth + 1
from T join Links
on Links.link = T.id
where depth = ::depth-1;
end;
I think this will be better than any cursor solution. In fact, I can't really think of how cursors would be useful for this problem at all.
Note that this will not terminate if there are any cycles (redirects that are ultimately circular).

I'd say you should create a user-defined function in this vein:
create function FindLastId (ID as integer) returns integer as $$
declare newid integer;
declare primaryid integer;
declare continue boolean;
begin
set continue = true;
set primaryid = $1;
while (continue)
select into newid redirectid from table where id = :primaryid;
if newid is null then
set continue = false;
else
set primaryid = :newid;
end if;
end loop;
return primaryid;
end;
$$ language pgplsql;
I'm a bit shaky on the Postgres syntax, so you may have some cleanup to do. Anyway, you can then call your function like so:
select id, FindLastId(id) as EndId from table
On a table like so:
id redirectid data
1 3 ab
2 null cd
3 2 ef
4 1 gh
5 null ij
This will return:
id EndId
1 2
2 2
3 2
4 2
5 5
Note that this will be markedly slow, but it should get you the ID's pretty quickly for a small result set on a well indexed table.

Related

SQL - Retrieve records based on parameters where either parameter can be null

I have a stored procedure which takes in five parameters of which two can be null - we will call these parameters A and B
What I would like to do is select records based on the following logic.
If Parameter A is NULL then only return records that match Parameter B
I know that I can do something similar to the following
IF A IS NULL
BEGIN
SELECT * FROM TABLE WHERE Param=B
END
ELSE
BEGIN
SELECT * FROM TABLE WHERE Param=A
END
However, the SQL query is much more complex then the above one and there would be huge replication in the Proc which is something I want to avoid
Thanks in advance
===============================
EDIT - Sorry, I should have mentioned that in the example the Param are based on separate columns e.g.
My table consists of four columns of which two separate columns map to the two parameters - basic schema below
ID
PersonName
GroupID
DeliveryID
In my procedure I want to retrieve those records that match the GroupID however in the scenario where the GroupID is null then I want to return those records that match the DeliveryID
Thanks again
Try
SELECT * FROM my_table WHERE Param = COALESCE(A,B)
COALESCE will give you A if it's not null. Otherwise B.
Functionally, something like this should work. If either parameter is NULL, the condition becomes a self-identity (assuming neither groupID nor deliveryID is NULL).
SELECT *
FROM table_name
WHERE groupID = coalesce(#groupIDParameter, groupID)
AND deliveryID = coalesce(#deliveryIDParameter, deliveryID)
Try ISNULL function:
SELECT * FROM TABLE WHERE Param = ISNULL(B,A)
You could also use a case statement Case when A is Null Then B

teradata case when issue

I have the following queries which are supposed to give the same result, but drastically different
1.
select count(*)
from qigq_sess_parse_2
where str_vendor = 'natural search' and str_category is null and destntn_url = 'http://XXXX.com';
create table qigq_test1 as
(
select case
when (str_vendor = 'natural search' and str_category is null and destntn_url = 'http://XXXX.com' ) then 1
else 0
end as m
from qigq_sess_parse_2
) with data;
select count(*) from qigq_test1 where m = 1;
the first block gives a total number of count 132868, while the second one only gives 1.
What are the subtle parts in the query that causes this difference?
Thanks
When you create a table in Teradata, you can specify it to be SET or MULTISET. If you don't specify, it defaults to SET. A set table cannot contain duplicates. So at most, your new table will contain two rows, a 0 and a 1, since that's all that can come from your case statement.
EDIT:
After a bit more digging, the defaults aren't quite that simple. But in any case, I suspect that if you add the MULTISET option to your create statement, you'll see the behavior your expect.
My guess would be that your Create Table statement is only pulling in one row of data that fits the parameters for the following Count statement. Try this instead:
CREATE TABLE qigq_test1 (m integer);
INSERT INTO qigq_test1
SELECT
CASE
WHEN (str_vendor = 'natural search' and str_category IS NULL AND destntn_url = 'http://XXXX.com' ) THEN 1
ELSE 0
END AS m
FROM qigq_sess_parse_2;
SELECT COUNT(*) FROM qigq_test1 WHERE m = 1;
This should pull ALL ROWS of data from qigq_sess_parse_2 into qigq_test1 as either a 0 or 1.

Randomly Select a Row with SQL in Access

I have a small access database with some tables. I am trying the code in the sql design within access. I just want to randomly select a record within a table.
I created a simple table called StateAbbreviation. It has two columns: ID and Abbreviation. ID is just an autonumber and Abbreviation are different abbreviations for states.
I saw this thread here. So I tried
SELECT Abbreviation
FROM STATEABBREVIATION
ORDER BY RAND()
LIMIT 1;
I get the error Syntax error (missing operator) in query expresion RAND() LIMIT 1. So I tired RANDOM() instead of RAND(). Same error.
None of the others worked either. What am I doing wrong? Thanks.
Ypercude provided a link that led me to the right answer below:
SELECT TOP 1 ABBREVIATION
FROM STATEABBREVIATION
ORDER BY RND(ID);
Note that for RND(), I believe that it has to be an integer value/variable.
You need both a variable and a time seed to not get the same sequence(s) each time you open Access and run the query - and to use Access SQL in Access:
SELECT TOP 1 Abbreviation
FROM STATEABBREVIATION
ORDER BY Rnd(-Timer()*[ID]);
where ID is the primary key of the table.
Please try this, it is helpful to you
It is possible by using a stored procedure and function, which I created it's have a extra column which you could be create in your table FLAG name and column all field value should be 0 Then it works
create Procedure proc_randomprimarykeynumber
as
declare #Primarykeyid int
select top 1
#Primarykeyid = u.ID
from
StateAbbreviation u
left join
StateAbbreviation v on u.ID = v.ID + 1
where
v.flag = 1
if(#Primarykeyid is null )
begin
UPDATE StateAbbreviation
SET flag = 0
UPDATE StateAbbreviation
SET flag = 1
WHERE ID IN (SELECT TOP 1 ID
FROM dbo.StateAbbreviation)
END
ELSE
BEGIN
UPDATE StateAbbreviation
SET flag = 0
UPDATE StateAbbreviation
SET flag = 1
WHERE ID IN (#Primarykeyid)
END
SET #Primarykeyid = 1
SELECT TOP 1
ID, Abbreviation
FROM
StateAbbreviation
WHERE
flag = 1
It is made in stored procedure run this and get serial wise primary key
exec proc_randomprimarykeynumber
Thanks and regard
Try this:
SELECT TOP 1 *
FROM tbl_name
ORDER BY NEWID()
Of course this may have performance considerations for large tables.

Case statement inside create-table

How do you use the "java if-statement" in SQL => PostgreSQL, while creating a table/Column?
CREATE TABLE Store(
Discount INT
AS CASE
WHEN SOLD_Amount>100000 THEN 2
WHEN SOLD_Amount>500000 THEN 5
WHEN SOLD_Amount>1000000 THEN 10
ELSE 0
END
NOT NULL)
This is probally wrong, please tell us, the community how to do this kind of action.
What you are looking for here is a computed column, which is not directly supported by Postgres. You could implement this in a view, like so:
CREATE VIEW someview AS
SELECT SOLD_Amount,
CASE
WHEN SOLD_Amount>100000 THEN 2
WHEN SOLD_Amount>500000 THEN 5
WHEN SOLD_Amount>1000000 THEN 10
ELSE 0
END As Discount
Or you could use a trigger to populate the column on insert/update.
You can use a special PostgreSQL feature: "generated" columns.
Based on an existing table, say:
CREATE TABLE store (sold_amount int, ...):
You could create this special function:
CREATE FUNCTION store_sold_amount(rec store)
RETURNS int LANGUAGE SQL IMMUTABLE
AS
$func$
SELECT CASE
WHEN rec.sold_amount > 100000 THEN 2
WHEN rec.sold_amount > 500000 THEN 5
WHEN rec.sold_amount > 1000000 THEN 10
ELSE 0 END;
$func$;
Then you can query:
SELECT s.amount, s.store_sold_amount
FROM store s;
More under these related questions:
How can I create a column in postgres from values and selections based on other columns?
Store common query as column?
SELECT A.*,
CASE
WHEN B.Table2 IS NOT NULL
THEN 'Yes'
ELSE 'No'
END AS results_column_name_here
INTO new_table_name
FROM Table1 A
LEFT JOIN Table2 B
ON A.col_to_join_on = B.col_to_join_on

Avoiding repetitive conditions in the select case and where clause

I have a table say TAB1 with the following columns -
USER_ID NUMBER(5),
PHN_NO1 CHAR(20),
PHN_NO2 CHAR(20)
I have to fetch records from TAB1 into another table TAB2 such that all records with either one of the two or both PHN_NO1 and PHN_NO2 are of length 10 and begin with 5.
If in a record,say only PHN_NO1 satisfies the condition and PHN_NO2 does not then, TAB2.P1 should be same as TAB1.PHN_NO1 but TAB2.P2 should be NULL.
If neither of the two satisfy the condition, then the record should not be inserted into TAB2
Structure of TAB2 would be as
USER_ID number(5)- holding the ROWID of the record selected from TAB1
P1 char(10)- holding TAB1.PHN_NO1 if it is of length 10 and begins with 5, otherwise NULL
P2 char(10)- holding TAB1.PHN_NO2 if it is of length 10 and beigns with 5, otherwise NULL
I could write the below query to achieve the above, but the conditions in the CASE and WHERE are repetitive. Please suggest a way to achieve the above in a better way.
CREATE TABLE TAB2
AS
SELECT
USER_ID,
CASE WHEN
(LENGTH(TRIM(PHN_NO1)) = 10 AND TRIM(PHN_NO1) like '5%')
THEN
CAST(TRIM(PHN_NO1) as CHAR(10))
ELSE
CAST(NULL as CHAR(10))
END AS P1,
CASE (LENGTH(TRIM(PHN_NO2)) = 10 AND TRIM(PHN_NO2) like '5%')
THEN
CAST(TRIM(PHN_NO2) as CHAR(10))
ELSE
CAST(NULL as CHAR(10))
END AS P2
WHERE
(LENGTH(TRIM(PHN_NO1) = 10 AND TRIM(PHN_NO1) like '5%')
OR
(LENGTH(TRIM(PHN_NO2) = 10 AND TRIM(PHN_NO2) like '5%')
Sure you can! You do have to use some conditions though:
INSERT INTO New_Phone
SELECT user_id, phn_no1, phn_no2
FROM (SELECT user_id,
CASE WHEN LENGTH(TRIM(phn_no1)) = 10 AND TRIM(phn_no1) like '5%'
THEN SUBSTR(phn_no1, 1, 10) ELSE NULL END phn_no1,
CASE WHEN LENGTH(TRIM(phn_no2)) = 10 AND TRIM(phn_no2) like '5%'
THEN SUBSTR(phn_no2, 1, 10) ELSE NULL END phn_no2
FROM Old_Phone) Old
WHERE phn_no1 IS NOT NULL
OR phn_no2 IS NOT NULL;
(I have a working SQL Fiddle example.)
This should work on any RDBMS. Note that, because of your data, this isn't likely to be less performant than your original (which would not have used an index, given the TRIM()). It's also not likely to be better, given that most major RDBMSs are able to re-use the results of deterministic functions per-row.
Oh, it should be noted that, internationally, phone numbers can be up to 15 digits in length (with a minimum in-country of 6 or less). Maybe use VARCHAR (and save yourself some TRIM()s, too)? And INTEGER (or BIGINT, maybe TINYINT) is more often used for surrogate ids, NUMBER is a little strange.