Using A couple of Temp variables in PostgreSQL - postgresql

Trying to get this working. I realize that select into creates a table, not a variable so I'm not sure how to do this:
select state.state_id into stateId from state where state.name = 'Illinois'
INSERT INTO public.city(
name,
state_id)
VALUES ('Chicago', stateId);
select public.city.city_id into chicago from public.city where public.city.name = 'Chicago'
INSERT INTO public.location (
city_id,
state_id,
country_id,
name)
VALUES (
chicago,
9,
185,
'Chicago, IL, USA'
);

No need for variables here, you can query as you're inserting. Just be sure that your query returns only the one row you want.
INSERT INTO public.city (name, state_id)
SELECT 'Chicago', state.state_id
FROM state
WHERE state.name = 'Illinois';
INSERT INTO public.location (city_id, state_id, country_id, name)
SELECT public.city.city_id, 9, 185, 'Chicago, IL, USA'
FROM public.city
WHERE public.city.name = 'Chicago';

Related

JOIN with array of ids returns duplicate root records instead of just one

I'm trying to join several tables and pull out each DISTINCT root record (from table_a), but for some reason I keep getting duplicates. Here is my select query:
Fiddle
select
ta.id,
ta.table_a_name as "tableName"
from my_schema.table_a ta
left join my_schema.table_b tb
on (tb.table_a_id = ta.id)
left join my_schema.table_c tc
on (tc.table_b_id = tb.id)
left join my_schema.table_d td
on (td.id = any(tc.table_d_ids))
where td.id = any(array[100]);
This returns the following:
[
{
"id": 2,
"tableName": "Root record 2"
},
{
"id": 2,
"tableName": "Root record 2"
}
]
But I am only expecting, in this case,
[
{
"id": 2,
"tableName": "Root record 2"
}
]
What am I doing wrong here?
Here's the fiddle and, just in case, the create and insert statements below:
create schema if not exists my_schema;
create table if not exists my_schema.table_a (
id serial primary key,
table_a_name varchar (255) not null
);
create table if not exists my_schema.table_b (
id serial primary key,
table_a_id bigint not null references my_schema.table_a (id)
);
create table if not exists my_schema.table_d (
id serial primary key
);
create table if not exists my_schema.table_c (
id serial primary key,
table_b_id bigint not null references my_schema.table_b (id),
table_d_ids bigint[] not null
);
insert into my_schema.table_a values
(1, 'Root record 1'),
(2, 'Root record 2'),
(3, 'Root record 3');
insert into my_schema.table_b values
(10, 2),
(11, 2),
(12, 3);
insert into my_schema.table_d values
(100),
(101),
(102),
(103),
(104);
insert into my_schema.table_c values
(1000, 10, array[]::int[]),
(1001, 10, array[100]),
(1002, 11, array[100, 101]),
(1003, 12, array[102]),
(1004, 12, array[103]);
Short answer is use distinct, and this will get the results you want:
select distinct
ta.id,
ta.table_a_name as "tableName"
from my_schema.table_a ta
left join my_schema.table_b tb
on (tb.table_a_id = ta.id)
left join my_schema.table_c tc
on (tc.table_b_id = tb.id)
left join my_schema.table_d td
on (td.id = any(tc.table_d_ids))
where td.id = any(array[100]);
That said, this doesn't sit well with me because I assume this is not the end of your query.
The root issue is that you have two records from table_b - table_d that match this criteria. If you follow the breadcrumbs back, you will see there really are two matches:
select
ta.id,
ta.table_a_name as "tableName", tb.*, tc.*, td.*
from my_schema.table_a ta
left join my_schema.table_b tb
on (tb.table_a_id = ta.id)
left join my_schema.table_c tc
on (tc.table_b_id = tb.id)
left join my_schema.table_d td
on (td.id = any(tc.table_d_ids))
where td.id = any(array[100]);
So 'distinct' is just a lazy fix to say if there are dupes, limit it to one...
My next question is, is there more to it than this? What's supposed to happen next? Do you really just want candidates from table_a, or is this part 1 of a longer issue? If there is more to it, then there is likely a better solution than a simple select distinct.
-- edit 10/1/2022 --
Based on your comment, I have one final suggestion. Because this really all there is to your output AND you don't actually need the data from the b/c/d tables, then I think a semi-join is a better solution.
It's slightly more code (not going to win any golf or de-obfuscation contents), but it's much more efficient than a distinct or group by all columns. The reason is a distinct pulls every row result and then has to order and remove dupes. A semi-join, by contrast, will "stop looking" once it finds a match. It also scales very well. Almost every time I see a distinct misused, it's better served by a semi-join.
select
ta.id,
ta.table_a_name as "tableName"
from my_schema.table_a ta
where exists (
select null
from
table_b tb,
table_c tc,
table_d tc
where
tb.table_a_id = ta.id and
tc.table_b_id = tb.id and
td.id = any(tc.table_d_ids) and
td.id = any(array[100])
)
I didn't suggest this initially because I was unclear on the "what next."

Joining two one-to-many tables duplicates records

I have 3 tables, Transaction, Transaction_Items and Transaction_History.
Where the Transaction is the parent table, while Transaction_Items and Transaction_History are the children tables, with one to many relationship.
When i try to join those tables together, if i have 2+ Transaction_History records, or 2+ Transaction_Items i get duplicated or triplicated record results.
This is the SQL query im currently using which works, but what worries me that in the future if i have to Join another one-to-many table, it will duplicate the results again.
I found a workaround for this, but i was just wondering if there is a better and cleaner way to do this ?
The results should be a PostgreSQL JSON array which will contain the Transaction_Items and Transaction_History
SELECT
TR.id AS transaction_id,
TR.transaction_number,
TR.status,
TR.status AS status,
to_json(TR_INV.list),
COUNT(TR_INV) item_cnt,
COUNT(THR) tr_cnt,
json_agg(THR)
FROM transaction_transaction AS TR
LEFT JOIN (
SELECT
array_agg(t) list, -- this is a workaround method
t.transaction_id
FROM (
SELECT
TR_INV.transaction_id transaction_id,
IT.id,
IT.stock_number,
CAT.key category_key,
ITP.description description,
ITP.serial_number serial_number,
ITP.color color,
ITP.manufacturer manufacturer,
ITP.inventory_model inventory_model,
ITP.average_cost average_cost,
ITP.location_in_store location_in_store,
ITP.firearm_caliber firearm_caliber,
ITP.federal_firearm_number federal_firearm_number,
ITP.sold_price sold_price
FROM transaction_transaction_item TR_INV
LEFT JOIN inventory_item IT ON IT.id = TR_INV.item_id
LEFT JOIN inventory_itemprofile ITP ON ITP.id = IT.current_profile_id
LEFT JOIN inventory_category CAT ON CAT.id = ITP.category_id
LEFT JOIN inventory_categorytype CAT_T ON CAT_T.id = CAT.category_type_id
) t
GROUP BY t.transaction_id
) TR_INV ON TR_INV.transaction_id = TR.id
LEFT JOIN transaction_transactionhistory THR ON THR.transaction_id = TR.id
AND (THR.audit_code_id = 44 OR THR.audit_code_id = 27 OR THR.audit_code_id = 28)
WHERE TR.store_id = 21
AND TR.transaction_type = 'Pawn_Loan' AND TR.date_made >= '2018-10-08'
GROUP BY TR.id, TR_INV.list
What you want to do can be achieved by not using joins, as shown below.
Because your actual tables have so many columns that I don't know and should not care. I just created the simplest forms of them for demonstration.
CREATE TABLE transactions (
tid serial PRIMARY KEY,
name varchar(40) NOT NULL
);
CREATE TABLE transaction_histories (
hid serial PRIMARY KEY ,
tid integer REFERENCES transactions(tid),
history varchar(40) NOT NULL
);
CREATE TABLE transaction_items (
iid serial PRIMARY KEY ,
tid integer REFERENCES transactions(tid),
item varchar(40) NOT NULL
);
INSERT INTO transactions(tid,name) Values(1, 'transaction');
INSERT INTO transaction_histories(tid, history) Values(1, 'history1');
INSERT INTO transaction_histories(tid, history) Values(1, 'history2');
INSERT INTO transaction_items(tid, item) Values(1, 'item1');
INSERT INTO transaction_items(tid, item) Values(1, 'item2');
select
t.*,
(select count(*) from transaction_histories h where h.tid= t.tid) h_count ,
(select json_agg(h) from transaction_histories h where h.tid= t.tid) h ,
(select count(*) from transaction_items i where i.tid= t.tid) i_count ,
(select json_agg(i) from transaction_items i where i.tid= t.tid) i
from transactions t;

how to create parentchild hierachery in ssrs single parameter

I have one doubt in ssrs .
how to create parent hierachery in single parameter in ssrs with table report using sql server database table.
table: empdetails:
CREATE TABLE [dbo].[empdetails](
[empid] [int] NULL,
[country] [varchar](50) NULL,
[state] [varchar](50) NULL,
[empname] [varchar](50) NULL,
[deptno] [int] NULL
) ON [PRIMARY]
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (1, N'india', N'ap', N'abc', 10)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (2, N'india', N'ka', N'def', 20)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (3, N'india', N'tn', N'de', 30)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (4, N'usa', N'TX', N'deet', 50)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (5, N'usa', N'NJ', N'ion', 60)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (6, N'usa', N'WV', N'xy', 70)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (7, N'uk', N'Belfast', N'io', 40)
GO
INSERT [dbo].[empdetails] ([empid], [country], [state], [empname], [deptno]) VALUES (8, N'uk', N'Wycombe', N'un', 50)
GO
based on above table : I want create parent hierrachery in singe paramerte like below in ssrs table report :
country....>State: drop down look like below:
paramername:countryandstateparamer:
india
ap
ka
tn
usa
nj
wv
tx
uk
Belfast
Wycombe
here If I select india then dispaly corresponding states data (ap,ka,tn).
if I select ap state then display only ap related data(ap)
if I select ap and ka state then display only ap and ka data( ap and ka)
similay to other counry and states.
I tried like below :
dataset : select * from empdetails where country in #country and state in ( #state)
dataset 1: select distinct country from empdetails
dataset2 : select distinct state from empdetails where county=#country
after that I go throught two paramerts mapping
here preview time I got two paramater one is country paramaterand 2nd is state paramater.But I want only creat only one paramater that parater show like counryandstate hierchar using sql server table data.
please tell me how to solve this issue in ssrs report multiple hieracher in single paramater .
There is no built-in way to build a hierarchy with SQL Server as the source (SSAS supports this though).
We can do with with a bit of code, the first part could be done using a view but this should work even though it seems like a lot of code, most of it is repeated (hence why a view would be better)
Anyway,
First we need a dataset to supply data to the parameter list.
First dataset
Create a dataset called dsRegions
Set the dataset query to be
/* This could be built as a view to simplfy things */
-- create a table for the results
DECLARE #regions TABLE(RegionDisplayName varchar(50), RegionType varchar(1), RegionOrder decimal (10,3), RegionName varchar(50))
-- first get the countries in order and assign a sort order
INSERT INTO #regions
SELECT *
, ROW_NUMBER() OVER(ORDER BY country) AS CountryOrder
, country
FROM
(SELECT DISTINCT Country, 'C' AS RegionType FROM empdetails) e
-- now get the states and assign the sort order from the country plus a sort oder for the state (supports 999 states/country)
INSERT INTO #regions
SELECT
' ' + state, 'S' -- indent the states so they look better in the parameter list
, RegionOrder + CAST(ROW_NUMBER() OVER(PARTITION BY c.RegionName ORDER BY state) as decimal(10,3)) / 1000 AS StateOrder
, state
FROM #regions c
JOIN empdetails e on c.RegionName = e.country
-- finally output the results
SELECT * FROM #regions order by regionorder
Parameter : let's call this #pRegion
So create a parameter called pRegion and set its available values dataset to dsRegions.
important Set the Value to RegionName and the label to RegionDisplayName
This will give us a nice ordered indented list for the user to see.
Main dataset query
The main dataset query repeats most of the parameter dataset query
The only difference is the final output which filters both country and state and if either match it will return the records. The query is as follows
-- create a table for the results
DECLARE #regions TABLE(RegionDisplayName varchar(50), RegionType varchar(1), RegionOrder decimal (10,3), RegionName varchar(50))
-- first get the countries in order and assign a sort order
INSERT INTO #regions
SELECT *
, ROW_NUMBER() OVER(ORDER BY country) AS CountryOrder
, country
FROM
(SELECT DISTINCT Country, 'C' AS RegionType FROM empdetails) e
-- now get the states and assign the sort order from the country plus a sort oder for the state (supports 999 states/country)
INSERT INTO #regions
SELECT
' ' + state, 'S' -- indent the states so they look better in the parameter list
, RegionOrder + CAST(ROW_NUMBER() OVER(PARTITION BY c.RegionName ORDER BY state) as decimal(10,3)) / 1000 AS StateOrder
, state
FROM #regions c
JOIN empdetails e on c.RegionName = e.country
-- output
SELECT
e.*
FROM empdetails e
WHERE country in (#pRegion)
or state in (#pRegion)
I've done a simple test and this works as expected, so if you select UK and TX you will get records for Belfast, Wycombe and TX
Hope this helps.

Postgresql recursive CTE results ordering

I'm working on a query to pull data out of a hierarchy
e.g.
CREATE table org (
id INT PRIMARY KEY,
name TEXT NOT NULL,
parent_id INT);
INSERT INTO org (id, name) VALUES (0, 'top');
INSERT INTO org (id, name, parent_id) VALUES (1, 'middle1', 0);
INSERT INTO org (id, name, parent_id) VALUES (2, 'middle2', 0);
INSERT INTO org (id, name, parent_id) VALUES (3, 'bottom3', 1);
WITH RECURSIVE parent_org (id, parent_id, name) AS (
SELECT id, parent_id, name
FROM org
WHERE id = 3
UNION ALL
SELECT o.id, o.parent_id, o.name
FROM org o, parent_org po
WHERE po.parent_id = o.id)
SELECT id, parent_id, name
FROM parent_org;
It works as expected.
3 1 "bottom3"
1 0 "middle1"
0 "top"
It's also returning the data in the order that I expect, and it makes sense to me that it would do this because of the way that the results would be discovered.
The question is, can I count on the order being like this?
Yes, there is a defined order. In the Postgres WITH doc, they give the following example:
WITH RECURSIVE search_graph(id, link, data, depth, path, cycle) AS (
SELECT g.id, g.link, g.data, 1,
ARRAY[ROW(g.f1, g.f2)],
false
FROM graph g
UNION ALL
SELECT g.id, g.link, g.data, sg.depth + 1,
path || ROW(g.f1, g.f2),
ROW(g.f1, g.f2) = ANY(path)
FROM graph g, search_graph sg
WHERE g.id = sg.link AND NOT cycle
)
SELECT * FROM search_graph;
About which they say in a Tip box (formatting mine):
The recursive query evaluation algorithm produces its output in
breadth-first search order. You can display the results in depth-first
search order by making the outer query ORDER BY a "path" column
constructed in this way.
You do appear to be getting breadth-first output in your case above based on the INSERT statements, so I would say you could, if you wanted, modify your outer SELECT to order it in another fashion.
I believe the analog for depth-first in your case would probably be this:
WITH RECURSIVE parent_org (id, parent_id, name) AS (
SELECT id, parent_id, name
FROM org
WHERE id = 3
UNION ALL
SELECT o.id, o.parent_id, o.name
FROM org o, parent_org po
WHERE po.parent_id = o.id)
SELECT id, parent_id, name
FROM parent_org
ORDER BY id;
As I would expect (running things through in my head) that to yield this:
0 "top"
1 0 "middle1"
3 1 "bottom3"

Postgresql. select SUM value from arrays

Condition:
There are two tables with arrays.
Note food.integer and price.food_id specified array.
CREATE TABLE food (
id integer[] NOT NULL,
name character varying(255),
);
INSERT INTO food VALUES ('{1}', 'Apple');
INSERT INTO food VALUES ('{1,1}', 'Orange');
INSERT INTO food VALUES ('{1,2}', 'banana');
and
CREATE TABLE price (
id bigint NOT NULL,
food_id integer[],
value double precision DEFAULT 0
);
INSERT INTO price VALUES (44, '{1}', 500);
INSERT INTO price VALUES (55, '{1,1}', 100);
INSERT INTO price VALUES (66, '{1,2}', 200);
Need to get the sum value of all the products from table food.
Please help make a sql query.
ANSWER:
{1} - Apple - 800 (500+100+200)
What about this:
select
name,
sum(value)
from
(select unnest(id) as food_id, name from food) food_cte
join (select distinct id, unnest(food_id) as food_id, value from price) price_cte using (food_id)
group by
name
It is difficult to understand your question, but this query at least returns 800 for Apple.
try the following command,
SELECT F.ID,F.NAME,SUM(P.VALUE) FROM FOOD F,PRICE P WHERE F.ID=P.FOOT_ID;