Pl/PgSQL, insert into two tables, one with header rows - sql

What would be the best way of going about doing the following insert. I've looked around, and I'm kind of stuck.
The table I Currently have (insert)
| id | order_id | item_id | type | group |
| 1 | 1 | 1 | 2 | 1 | <- type 2 represents a "header" item, or a "kit"
| 2 | 1 | 2 | 1 | 1 | <- type 1 represents a member of the "kit"
| 3 | 1 | 3 | 1 | 1 |
| 4 | 1 | 4 | 2 | 2 | <- New group means new kit
| 5 | 1 | 2 | 1 | 2 |
| 6 | 1 | 5 | 1 | 2 |
I need to insert these items into the following two tables:
1) item_entry
| id | mode | tmplt_id | item_id | parent_item_entry_id |
| 1 | 1 | 1 | NULL | NULL | <- This is a header line, mode 1
| 2 | 2 | NULL | 2 | 1 | <- This is a sub line, mode 2
| 3 | 2 | NULL | 3 | 1 | <- parent_item_entry_id references the header it belongs to
| 4 | 1 | 4 | NULL | NULL |
| 5 | 2 | NULL | 2 | 4 |
| 6 | 2 | NULL | 5 | 4 |
2) item_entry_details
| id | item_entry_id | order_id | group |
| 1 | 1 | 1 | 1 |
| 2 | 4 | 1 | 2 | <- only header information is necessary
Of course, item_entry.id is driven off of a sequence (item_entry_id_seq). Is there an elegent way of getting this to work? I currently loop through each group, first assigning a nextval() to a variable, and then I loop through each item in the group, writing to the table.
FOR recGroup IN SELECT DISTINCT group FROM insert LOOP
intParentItemEntryID := nextval('item_entry_id_seq');
FOR recLine IN SELECT * FROM insert LOOP
INSERT INTO item_entry VALUES (CASE intParentItemEntryID/DEFAULT, CASE 1/2, CASE recLine.item_id/NULL, CASE NULL/recLine.item_id, CASE NULL/intParentItemEntryID)
INSERT INTO item_entry_details VALUES (DEFAULT, intParentItemEntryID, recLine.order_id, recLine.group);
END LOOP;
END LOOP;
Is there a better way, or is the above the only way this type of insert can be done?

No need for procedural code "row-at-a-time" here, just plain old sql will suffice.
-- create the tables that the OP did not provide
DROP TABLE the_input;
CREATE TABLE the_input
( id INTEGER NOT NULL PRIMARY KEY
, order_id INTEGER NOT NULL
, item_id INTEGER NOT NULL
, ztype INTEGER NOT NULL
, zgroup INTEGER NOT NULL
);
DROP TABLE target1 ;
CREATE TABLE target1
( id INTEGER NOT NULL
, zmode INTEGER NOT NULL
, tmplt_id INTEGER
, item_id INTEGER
, parent_item_entry_id INTEGER
);
DROP TABLE target2 ;
CREATE TABLE target2
( id SERIAL NOT NULL
, item_entry_id INTEGER NOT NULL
, order_id INTEGER NOT NULL
, zgroup INTEGER NOT NULL
);
-- fil it up ...
INSERT INTO the_input
( id, order_id, item_id, ztype, zgroup ) VALUES
( 1 , 1 , 1 , 2 , 1 ) -- <- type 2 represents a "header" item, or a "kit"
,( 2 , 1 , 2 , 1 , 1 ) -- <- type 1 represents a member of the "kit"
,( 3 , 1 , 3 , 1 , 1 ) --
,( 4 , 1 , 4 , 2 , 2 ) -- <- New group means new kit
,( 5 , 1 , 2 , 1 , 2 ) --
,( 6 , 1 , 5 , 1 , 2 ) --
;
-- Do the inserts.
INSERT INTO target1(id,zmode,tmplt_id,item_id,parent_item_entry_id)
SELECT i1.id, 1, i1.id, NULL, NULL
FROM the_input i1
WHERE i1.ztype=2
UNION ALL
SELECT i2.id, 2, NULL, i2.id, ip.item_id
FROM the_input i2
JOIN the_input ip ON ip.zgroup = i2.zgroup AND ip.ztype=2
WHERE i2.ztype=1
;
INSERT INTO target2(item_entry_id,order_id,zgroup)
SELECT DISTINCT MIN(item_id),order_id, zgroup
FROM the_input i1
WHERE i1.ztype=2
GROUP BY order_id,zgroup
;
SELECT * FROM target1
ORDER BY id;
SELECT * FROM target2
ORDER BY id;
Result:
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "the_input_pkey" for table "the_input"
CREATE TABLE
INSERT 0 6
DROP TABLE
CREATE TABLE
INSERT 0 6
id | zmode | tmplt_id | item_id | parent_item_entry_id
----+-------+----------+---------+----------------------
1 | 1 | 1 | |
2 | 2 | | 2 | 1
3 | 2 | | 3 | 1
4 | 1 | 4 | |
5 | 2 | | 5 | 4
6 | 2 | | 6 | 4
(6 rows)
DROP TABLE
NOTICE: CREATE TABLE will create implicit sequence "target2_id_seq" for serial column "target2.id"
CREATE TABLE
INSERT 0 2
id | item_entry_id | order_id | zgroup
----+---------------+----------+--------
1 | 4 | 1 | 2
2 | 1 | 1 | 1
(2 rows)

Related

How to only show Unique values in this existing SQL query

I am comparing two tables (dbo.new and dbo.old) and if the first three column match and the forth column doesnst match, it has to select it. Now this shows a lot of values, and I only want to display the unique values of column2. This is the code I have now:
SELECT dbo.new.[column1], dbo.new.[column2], dbo.new.[column3], dbo.new.[column4]
FROM dbo.new
JOIN dbo.old ON dbo.new.[column1]=dbo.old.[column1]
AND dbo.new.[column2]=dbo.old.[column2]
AND dbo.new.[column3]=dbo.old.[column3]
WHERE [dbo].[new].[column4] <> [dbo].[old].[column4]
First two tables I start with:
-----------------
| 1 | 1 | 1 | 1 |
-----------------
| 2 | 1 | 2 | 2 |
-----------------
| 3 | 3 | 3 | 3 |
-----------------
| 4 | 1 | 4 | 4 |
-----------------
-----------------
| 1 | 1 | 1 | 9 |
-----------------
| 2 | 1 | 2 | 9 |
-----------------
| 3 | 3 | 3 | 9 |
-----------------
| 4 | 1 | 4 | 9 |
-----------------
This is the outcome of the query above:
-----------------
| 1 | 1 | 1 | 1 |
-----------------
| 2 | 1 | 2 | 2 |
-----------------
| 3 | 3 | 3 | 3 |
-----------------
| 4 | 1 | 4 | 4 |
-----------------
^ delete those duplicates
This is what I want to be the outcome:
-----------------
| 1 | 1 | 1 | 1 |
-----------------
| 3 | 3 | 3 | 3 |
-----------------
I tried many things like UNIQUE and DISTINCT but I cant find the solution. It doenst even need to show the first value, as long as it show onea row with the unique number. So this is correct too:
-----------------
| 4 | 1 | 4 | 4 |
-----------------
| 3 | 3 | 3 | 3 |
-----------------
Choose the ordering you need in over() to get proper rows.
SELECT TOP(1) WITH TIES
dbo.new.[column1], dbo.new.[column2], dbo.new.[column3], dbo.new.[column4]
FROM dbo.new
JOIN dbo.old ON dbo.new.[column1]=dbo.old.[column1]
AND dbo.new.[column2]=dbo.old.[column2]
AND dbo.new.[column3]=dbo.old.[column3]
where [dbo].[new].[column4] <> [dbo].[old].[column4]
ORDER BY row_number() over(partition by dbo.new.[column2] order by dbo.new.[column1])
Quick demo, runs OK sql server 2014
create table dbo.new(
column1 int,
column2 int,
column3 int,
column4 int);
create table dbo.old(
column1 int,
column2 int,
column3 int,
column4 int);
insert dbo.new values
( 1 , 1 , 1 , 1 ),
( 2 , 1 , 2 , 2 ),
( 3 , 3 , 3 , 3 ),
( 4 , 1 , 4 , 4 );
insert dbo.old values
( 1 , 1 , 1 , 9 ),
( 2 , 1 , 2 , 9 ),
( 3 , 3 , 3 , 9 ),
( 4 , 1 , 4 , 9 );
SELECT TOP(1) WITH TIES
dbo.new.[column1], dbo.new.[column2], dbo.new.[column3], dbo.new.[column4]
FROM dbo.new
JOIN dbo.old ON dbo.new.[column1]=dbo.old.[column1]
AND dbo.new.[column2]=dbo.old.[column2]
AND dbo.new.[column3]=dbo.old.[column3]
where [dbo].[new].[column4] <> [dbo].[old].[column4]
ORDER BY row_number() over(partition by dbo.new.[column2] order by dbo.new.[column1]);
Result is
column1 column2 column3 column4
1 1 1 1
3 3 3 3
It looks like you're only interested in column 2 so let's start with only selecting that.
Then add a simple: GROUP BY at the end and you're done.
SELECT N.[column2] as myvalue
FROM dbo.new N
JOIN dbo.old O
ON N.[column1]=O.[column1]
AND N.[column2]=O.[column2]
AND N.[column3]=O.[column3]
WHERE N.[column4] <> O.[column4]
GROUP BY N.[column2]

Convert tuple value to column names

Got something like:
+-------+------+-------+
| count | id | grade |
+-------+------+-------+
| 1 | 0 | A |
| 2 | 0 | B |
| 1 | 1 | F |
| 3 | 1 | D |
| 5 | 2 | B |
| 1 | 2 | C |
I need:
+-----+---+----+---+---+---+
| id | A | B | C | D | F |
+-----+---+----+---+---+---+
| 0 | 1 | 2 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 1 | 1 |
| 2 | 0 | 5 | 1 | 0 | 0 |
I don't know if I can even do this. I can group by id but how would you read the count value for each grade column?
CREATE TABLE #MyTable(_count INT,id INT , grade VARCHAR(10))
INSERT INTO #MyTable( _count ,id , grade )
SELECT 1,0,'A' UNION ALL
SELECT 2,0,'B' UNION ALL
SELECT 1,1,'F' UNION ALL
SELECT 3,1,'D' UNION ALL
SELECT 5,2,'B' UNION ALL
SELECT 1,2,'C'
SELECT *
FROM
(
SELECT _count ,id ,grade
FROM #MyTable
)A
PIVOT
(
MAX(_count) FOR grade IN ([A],[B],[C],[D],[F])
)P
You need a "pivot" table or "cross-tabulation". You can use a combination of aggregation and CASE statements, or, more elegantly the crosstab() function provided by the additional module tablefunc. All basics here:
PostgreSQL Crosstab Query
Since not all keys in grade have values, you need the 2-parameter form. Like this:
SELECT * FROM crosstab(
'SELECT id, grade, count FROM table ORDER BY 1,2'
, $$SELECT unnest('{A,B,C,D,F}'::text[])$$
) ct(id text, "A" int, "B" int, "C" int, "D" int, "F" int);

Select unique results and null

I need get all lines from table, that have unique value in certain fields and all lines, than have null in this fields. Example:
id | name | group
-----------------
1 | One | 1
2 | Two | null
3 | Three| 3
4 | Four | 2
5 | Five | 1
6 | Six | 2
7 | Seven| null
Result:
id | name | group
-----------------
1 | One | 1
2 | Two | null
3 | Three| 3
4 | Four | 2
7 | Seven| null
How to make it in one request?
select t.id, t.name, t.`group`
from tablename t
join (select `group`, min(id) as mid
from tablename
where `group` is not null
group by `group`) x on x.mid = t.id and x.`group` = t.`group`
union all
select id, name, `group`
from tablename
where `group` is null

Variable number of columns in pivot

I have two tables.
First table is a dictionary:
test_1=> select * from first;
id | name
----+-------
1 | name1
2 | name2
3 | name3
Second table which is referenced to first table:
test_1=> select * from second;
id | number | first_id
----+--------+----------
1 | 1 | 2
2 | 1 | 3
3 | 1 | 2
4 | 2 | 3
5 | 2 | 2
6 | 3 | 3
Now I use pivot to show results:
SELECT *
FROM crosstab('SELECT
number, name, sum(number)::integer
FROM first
LEFT JOIN second ON second.first_id = first.id
GROUP BY 2, 1
ORDER BY 1, 2', $$SELECT unnest('{name1,name2,name3}'::text[])$$) AS ta (
number INTEGER,
name1 INTEGER,
name2 INTEGER,
name3 INTEGER
);
And result:
number | name1 | name2 | name3
--------+-------+-------+-------
1 | | 2 | 1
2 | | 2 | 2
3 | | | 3
And it's ok.
But now I must manually define columns - name1, name2, name3..., but columns are form dictionary table which can be changed.
So my question is - there is any possibility to get columns to pivot dynamically?

find set of row, duplicate list, before insert

I have table (it's a list of struct with 4 integers, first id is list id)
id | idL | idA(null) | idB(null) | idC
1 | 1 | 2 | null | 1
2 | 1 | 4 | null | 1
3 | 1 | null | 1 | 1
4 | 2 | 2 | null | 1
5 | 2 | 4 | null | 1
6 | 3 | 6 | null | 1
7 | 3 | null | 4 | 1
Now I need to insert 4th list to this table
idA | idB | idC
2 | null | 1
4 | null | 1
null | 1 | 1
but, it's already exist (list id = 1)
idA | idB | idC
2 | null | 1
4 | null | 1
alse exist (idL = 2)
idA | idB | idC
2 | null | 1
4 | null | 1
null | 7 | 1
does not exist.
How to find duplicate before insert it to table
It appears to be just a matter of insert from (select not in).
Try this example:
SQLFiddle
Disclaimer: In the example data you provided rows 2 and 4 got a identical idA,idB,idC set.
If that columns cannot form a unique and you already got that tuple in copy table and you need one row in copy table for each row in original table that ill be a lot harder because for a such row in copy there's no way to tell the row in original it's related.
if values is in table temp and you know the list id.
you can use "Except"
eg:
insert into list (idL, idA, idB, idC)
select #list_id, t.idA, t.idB, t.idC
from
(
select idA, idB, idC
from #new_values
except
select idA, idB, idC
from list
) t