Reference a view inside of the view in postgresql - sql

I would like to do something like the following:
CREATE VIEW foo_view AS
SELECT
foo.id,
foo.carryover_id,
foo.amount,
(SELECT SUM(foo_view.total)
FROM foo_view
WHERE foo_view.carryover_id = foo.id
) AS carryover_amount,
(amount + carryover_amount) AS total;
However, this raises an error relation "foo_view" does not exist. I would think that RECURSIVE would come in handy here, but the documentation is more reference than tutorial and there don't seem to be any resources out there to help me. Thoughts?
EDIT
Here's a sqlfiddle with a relevant schema http://sqlfiddle.com/#!15/6834d/1

You are right, your article is what you needed.
I formatted the data first into a fooBar temp table:
SELECT b.id
, foo_id
, ISNULL(f.carryover_id, foo_id) as carryover_id
, b.amount
, ROW_NUMBER() OVER(
PARTITION BY ISNULL(f.carryover_id, foo_id)
ORDER BY b.id asc) as rowNmbr
INTO #fooBar
FROM #bar as b
INNER JOIN #foo as f
ON b.foo_id = f.id;
The first row in the WITH is the top level. Then it recursively goes through each level. The rowNmbr is the level.
WITH summarizedFoo(rowNmbr, id, foo_id, carryover_id, amount, totalAmount)
AS
(
SELECT f.rowNmbr
, f.id
, f.foo_id
, f.carryover_id
, ISNULL(f.amount,0) as amount
, ISNULL(f.amount,0) as totalAmount
FROM #fooBar as f
WHERE f.rowNmbr = 1
UNION ALL
SELECT f.rowNmbr
, f.id
, f.foo_id
, f.carryover_id
, ISNULL(f.amount,0) as amount
, CAST((ISNULL(f.amount,0)
+ ISNULL(s.totalAmount,0)) as decimal) as totalAmount
FROM #fooBar as f
INNER JOIN summarizedFoo as s
ON f.carryover_id = s.carryover_id
AND f.rowNmbr = s.rowNmbr +1
)
SELECT rowNmbr, id, foo_id, carryover_id, amount, totalAmount
FROM summarizedFoo
I did this in T-SQL, but to make this work in your fiddler
Here is the PostgreSQL version:
Create a fooBar table (could be a view):
INSERT INTO fooBar
SELECT CAST(b.id as INTEGER)
, CAST(foo_id as INTEGER)
, CAST(CASE WHEN f.carryover_id is NULL THEN foo_id ELSE f.carryover_id END as INTEGER) as carryover_id
, CAST(b.amount as DECIMAL)
, CAST(ROW_NUMBER() OVER(PARTITION BY CASE WHEN f.carryover_id is NULL THEN foo_id ELSE f.carryover_id END ORDER BY b.id asc) as INTEGER) as rowNmbr
FROM bar as b
INNER JOIN foo as f
ON b.foo_id = f.id;
Here is the WITH RECURSIVE:
WITH RECURSIVE summarizedFoo(rowNmbr, id, foo_id, carryover_id, amount, totalAmount)
AS
(
SELECT f.rowNmbr, f.id, f.foo_id, f.carryover_id
, f.amount
, f.amount
FROM fooBar as f
WHERE f.rowNmbr = 1
UNION ALL
SELECT f.rowNmbr, f.id, f.foo_id, f.carryover_id
, f.amount
, f.amount + s.totalAmount
FROM fooBar as f
INNER JOIN summarizedFoo as s
ON f.carryover_id = s.carryover_id
AND f.rowNmbr = s.rowNmbr +1
)
SELECT rowNmbr, id, foo_id, carryover_id, amount, totalAmount
FROM summarizedFoo
I tested in SSMS(T-SQL version) and Fiddler(PostgreSQL), and verified the results in Excel.
Here is Fiddler of the solution:
http://sqlfiddle.com/#!15/a7822/3

The trick is to populate a table with source > target pairs, then to use that table to calculate the total amount:
CREATE RECURSIVE VIEW leaf_view(target_id, source_id) AS (
SELECT id, id FROM foo
UNION ALL
SELECT leaf_view.target_id, foo.id
FROM leaf_view
JOIN foo ON foo.carryover_id = leaf_view.source_id
);
CREATE VIEW foo_view(id, total) AS
SELECT leaf_view.target_id, SUM(bar.amount)
FROM leaf_view
LEFT JOIN bar ON leaf_view.source_id = bar.foo_id
GROUP BY leaf_view.target_id;
http://sqlfiddle.com/#!15/6834d/62
I owe this answer to RhodiumToad in #postgresql on freenode. Note that this will have problems for large foo since the optimizer generally can't work on recursive queries.

It looks like you want to have a cumulative running total amount. You could get this with a self-join, or a sub query, e.g:
select
id,
amount,
(select sum amount from foo b where b.id <= a.id ) as cumulative
from foo a
or
select
a.id,
a.amount,
sum(b.amount) as cumulative
from foo a
join foo b on b.id <= a.id
group by a.id, a.amount

Editing my answer as below.. I realized you are trying to do something recursive, does this help?? It'd really help if you gave a sample input and the output that you are expecting..
WITH RECURSIVE foo_view(id, total) AS (
SELECT id, amount from foo;
UNION ALL
SELECT foo_view.id, sum(foo_view.total+foo.amount)
FROM foo
JOIN
foo_view
ON
foo_view.id = foo.carryover_id
)
SELECT * from foo_view

Related

How to add a temp table in SQL server

I am trying to add a temp table to my query so that I can query that temp table, I have searched the internet but I couldn't get a solution.
this is my query
;WITH cte AS (
SELECT ID, g.Name
FROM game.Game g WITH(NOLOCK
WHERE ID IN (SELECT Data FROM system.Split(1, ','))
UNION ALL
SELECT g.ID, g.Name
FROM game.Game g WITH(NOLOCK)
JOIN cte ON g.ParentID = cte.ID
)
SELECT c.ID,
c.Name
FROM cte c
INNER JOIN list.Type gt WITH(NOLOCK) ON c.TypeId = gt.TypeID
WHERE c.ID NOT IN (SELECT Data FROM system.Split(1, ','))
AND c.ID IN (SELECT ID FROM game.code WITH(NOLOCK)
WHERE ID = c.ID
AND StatusCode IN ('OP', 'CL', 'SU')
AND isDisplay = 'True'
AND GETDATE() BETWEEN DisplayStart AND DisplayEnd
AND GETDATE() < ISNULL(ResultDateTime, ResultExpected)
)
which gives me the following when I run it
ID | Name
1111 | BaseBall
2222 |BasketBall
45896 |Relay
now I tried to create a temp table as follows
Create Table #temp(
ID int,
Name varchar
)
;WITH cte AS (
SELECT ID, g.Name
FROM game.Game g WITH(NOLOCK)
WHERE ID IN (SELECT Data FROM system.Split(1, ','))
UNION ALL
SELECT g.ID, g.Name
FROM game.Game g WITH(NOLOCK)
JOIN cte ON g.ParentID = cte.ID
)
insert into #temp // i wanted to set these values in the temp table
SELECT c.ID,
c.Name
FROM cte c
INNER JOIN list.Type gt WITH(NOLOCK) ON c.TypeId = gt.TypeID
WHERE c.ID NOT IN (SELECT Data FROM system.Split(1, ','))
AND c.ID IN (SELECT ID FROM game.code WITH(NOLOCK)
WHERE ID = c.ID
AND StatusCode IN ('OP', 'CL', 'SU')
AND isDisplay = 'True'
AND GETDATE() BETWEEN DisplayStart AND DisplayEnd
AND GETDATE() < ISNULL(ResultDateTime, ResultExpected)
)
every time I try to store this information in the temp table it gives me an error 'Column name or number of supplied values does not match table definition.' But I only have two values in. What am I doing wrong that I cant see?
First, why not just use select into?
IF OBJECT_ID('TempDB..#temp') IS NOT NULL
BEGIN
DROP TABLE #temp
END
select c.ID, c.Name
into #temp
from . . .
Then you don't need to define #temp as a table.
Next, your definition is bad, because Name has only one character. This would be fixed with select into.
However, I don't know why you are getting the particular error you are getting. The numbers of columns appears to match.

SQL for a Join that contains a condition within one of the join conditions

I have a requirement to do a left join between two tables. TableA is a transactional table while TableB contains reference data. The logical rule for my join is as follow:
SELECT *
FROM
TableA a
LEFT JOIN TableB b
ON a.ItemCode = b.ItemCode
AND a.ItemType = b.ItemType
AND b.FundID = 1 (but if no match found use b.FundID = 99)
The last join condition, the part in brackets, is what I'm having trouble with.
EDIT: Some clarification - If no match is found on ItemCode & ItemType & FundID = 1 then I want to join on ItemCode & ItemType & FundID = 99. Also TableB might have two records that match on both ItemCode and ItemType with one record having a FundID = 1 and the second record having FundID = 2. I that case I only want the record with FundID = 1.
What would be the most efficient way to write this query?
The only thing I can come up with is to execute the query twice, once with FundID = 1 and then with FundID = 99. Then use a set operator to return all the records form the first query and only records from the second query that does not exist in the first one. The code will not be pretty and it does not seem efficient either.
Thanks for your ideas in advance.
Marius
If i do understand your requirement correctly, this should gives you what you want
SELECT *
FROM
TableA a
OUTER APPLY
(
SELECT TOP 1 *
FROM TableB b
WHERE a.ItemCode = b.ItemCode
AND a.ItemType = b.ItemType
AND b.FundID IN (1, 99)
ORDER BY b.FundID
) b
You can change the query to
AND b.FundID IN (1,99)
or
AND (b.FundID = 1 or b.FundID = 99)
This is the best solution I have received so far. Thanks to #HABO (see the comments section of my question).
Add a column to create a Row_Number() partitioned on ItemType and ItemCode and ordered by FundId, then use only the results with row number 1
For posterity:
-- Sample data.
declare #TableA as Table ( AId Int Identity, ItemCode VarChar(20), ItemType VarChar(20) );
declare #TableB as Table ( BId Int Identity, ItemCode VarChar(20), ItemType VarChar(20), FundId Int );
insert into #TableA ( ItemCode, ItemType ) values
( 'Nemo', 'Fish' ), ( 'Blinky', 'Fish' ), ( 'Muddy Mudskipper', 'Fish' ),
( 'Hammer', 'Tool' ), ( 'Screwdriver', 'Tool' ), ( 'Politician', 'Tool' ),
( 'Grape Nehi', 'Beverage' ), ( 'Screwdriver', 'Beverage' );
insert into #TableB ( ItemCode, ItemType, FundId ) values
( 'Blinky', 'Fish', 1 ), ( 'Muddy Mudskipper', 'Fish', 2 ),
( 'Hammer', 'Tool', 1 ), ( 'Screwdriver', 'Tool', 99 ),
( 'Politician', 'Tool', 1 ), ( 'Politician', 'Tool', 99 ),
( 'Grape Nehi', 'Beverage', 42 ), ( 'Screwdriver', 'Beverage', 1 );
select * from #TableA;
select * from #TableB;
-- Do the deed.
with JoinWithRanking as (
select A.AId, A.ItemCode, A.ItemType, B.BId, B.FundId,
Row_Number() over ( partition by A.ItemCode, A.ItemType order by B.FundId ) as RN
from #TableA as A left outer join
#TableB as B on B.ItemCode = A.ItemCode and B.ItemType = A.ItemType and
B.FundId in ( 1, 99 )
)
select AId, ItemCode, ItemType, BId, FundId
from JoinWithRanking
where RN = 1;

sql query- group by and then join

I have 2 tables as follow:
1)passenger - with passenger_id,passenger_name and passenger_city
2)flight - with flight_id,flight_name and passenger_id.
The question is:
List the passenger details with flight id, who has travelled in more than one flight.
(This function will display the passenger details with their flight id's who has travelled in more than one flight.)
I used this query:
select * from passenger_1038299
where passengerid in(select passengerid from flight_1038299
group by passengerid
having count(passengerid)>1);
but it doesnt give me flight_ids. please tell how to retrieve flight id as well.
thanks and sorry for stupid question as new to sql.
Join the flight table to get the passenger's flights
select * from passenger_1038299 p
join flight_1038299 f on f.passenger_id = p.passenger_id
where p.passengerid in(
select passengerid from flight_1038299 group by passengerid having count(passengerid)>1
);
I like to use exists to check for multiples. With an index on passenger_id it may run faster than the query above.
select * from passenger_1038299 p
join flight_1038299 f on f.passenger_id = p.passenger_id
where exists (
select 1 from flight_1038299 f2
where f2.passenger_id = f.passenger_id
and f2.flight_id <> f.flight_id
)
Edit
Another way using the count window function:
select * from (
select *,
count() over (partition by p.passenger_id) cnt
from passenger_1038299 p
join flight_1038299 f on f.passenger_id = p.passenger_id
) t where cnt > 1
Another way with using analytic functions:
SELECT * FROM (
SELECT p.*, f.flight_id,
count(*) OVER (PARTITION BY f.passenger_id ) As number_of_flights
FROM passenger p
JOIN flight f
ON p.passenger_id = f.passenger_id
)
WHERE number_of_flights > 1
Demo: http://sqlfiddle.com/#!4/dab21/11
Try this way
Flight id should be multiple so it comma separated of column.
select a.*,b.flightid from passenger_1038299 a
join (select passengerid,Stuff((SELECT ', ' + s.flight_id
FROM flight_1038299 l
where c.passengerid = l.passengerid
FOR XML PATH('')),1,1,'') flightid from flight_1038299 as c group by c.passengerid having count(c.passengerid)>1) b on a.passengerid=b.passengerid

Error on using union clause in cte sql query

I'm trying to do following but getting an max recursive error. Can someone please help?
Sample code to demonstrate what I'm trying to achieve:
DECLARE #SecurityMaster AS TABLE
(
ID INT,
SecurityAlias INT,
LegNumber INT
)
INSERT INTO #SecurityMaster
SELECT 12829, 3030106, NULL
UNION ALL
SELECT 12829, 3030107, 1
SELECT * FROM #SecurityMaster;
WITH CTE1 (ID, SecurityAlias, LegNumber)
AS
(
SELECT S.ID, S.SecurityAlias, S.LegNumber
FROM #SecurityMaster S
WHERE S.LegNumber IS NOT NULL
UNION ALL
select s.ID, s.SecurityAlias, s.LegNumber
from #SecurityMaster S inner join CTE1 c on s.ID = c.ID
where s.LegNumber is NULL
)
SELECT *
FROM CTE1;
Result I'm expecting:
ID SecurityAlias LegNumber
-----------------------------------------
12829 3030107 1
12829 3030106 NULL
Your questionis difficult to understand, but will this work for you?
select s.ID, s.SecurityAlias, s.LegNumber
from #SecurityMaster S
where s.LegNumber is NULL
And s.id in (SELECT f.ID
FROM #SecurityMaster f
WHERE f.LegNumber IS NOT NULL)

why are the results of the two queries different

the first query:
SELECT u.id , prop1.id
FROM ( SELECT '9fbc6e9b59504c08ac643752c1e2d033' AS id ,
'|6813dbbfec6241a19b8d2316d2cb2a65,1|' AS customprop
UNION
SELECT 'f2271c45682f45fc84527c4afff0ab16' AS id ,
'|6813dbbfec6241a19b8d2316d2cb2a65,2|' AS customprop
) u
INNER JOIN ( SELECT ROW_NUMBER() OVER ( ORDER BY a.Id ) id ,
A.Id propId ,
B.NAME
FROM ( SELECT '6813dbbfec6241a19b8d2316d2cb2a65' AS id ,
CONVERT(XML, '<v>1,职业资格1</v><v>2,职业资格2</v>') AS value
) A
OUTER APPLY ( SELECT Name = N.v.value('.',
'nvarchar(Max)')
FROM A.[VALUE].nodes('/v') N ( v )
) B
) prop1 ON CHARINDEX('|' + prop1.propid + ','
+ CONVERT(NVARCHAR(10), prop1.id)
+ '|', u.customprop) > 0
GROUP BY u.id ,
prop1.id
the second query:
SELECT u.id ,prop1.id, count(*)
FROM ( SELECT '9fbc6e9b59504c08ac643752c1e2d033' AS id ,
'|6813dbbfec6241a19b8d2316d2cb2a65,1|' AS customprop
UNION
SELECT 'f2271c45682f45fc84527c4afff0ab16' AS id ,
'|6813dbbfec6241a19b8d2316d2cb2a65,2|' AS customprop
) u
INNER JOIN ( SELECT ROW_NUMBER() OVER ( ORDER BY a.Id ) id ,
A.Id propId ,
B.NAME
FROM ( SELECT '6813dbbfec6241a19b8d2316d2cb2a65' AS id ,
CONVERT(XML, '<v>1,职业资格1</v><v>2,职业资格2</v>') AS value
) A
OUTER APPLY ( SELECT Name = N.v.value('.',
'nvarchar(Max)')
FROM A.[VALUE].nodes('/v') N ( v )
) B
) prop1 ON CHARINDEX('|' + prop1.propid + ','
+ CONVERT(NVARCHAR(10), prop1.id)
+ '|', u.customprop) > 0
GROUP BY u.id ,
prop1.id
sql can be executed on sqlserver 2005 directly.
the first query can produce one item and the second query produce two items.
I think that the two queries should both produce two items.
I have thouht for three days and I really want to konw why.
I'm a Chinese and my English is poor.I hope you can understand my description
Tough question, but the problem is with this line:
INNER JOIN ( SELECT ROW_NUMBER() OVER ( ORDER BY a.Id ) id ,
The ORDER BY is ambiguous and consequently, if it is executed multiple times (which it can be because of the INNER JOIN it is contained in), it may not always return the same ordering/assignment. This can cause a latter join condition to only match on one record instead of two, which is what happens in the query plan being used for the version without the count(*) column.
To fix this, you just need to add something to make the ordering assignment unique, like this:
INNER JOIN ( SELECT ROW_NUMBER() OVER ( ORDER BY a.Id, B.Name ASC ) id ,
Try it like this, it should work.
Your problem is with the ORDER BY clause of the ROW_NUMBER - since the a.ID is not unique the outcome is unpredictable. Make that unique and your problem will go away - you can use something like
...SELECT ROW_NUMBER() OVER ( ORDER BY newid() ) id...