Multi-INSERT with unchangeable param - postgresql

Is there any way to INSERT multiple values with one from DB that unchangable?
I thought about WITH but without success:
WITH t as (SELECT date_trunc('hour', NOW()))
INSERT INTO my_table(ID, TIME) VALUES (1,t),(2,t);

No need for the CTE, just use a plain SELECT as the source for the insert:
insert into my_table (id, time)
select i, date_trunc('hour', NOW())
from generate_series(1,2) i;
If you really want the CTE, you need to select from it in the values clause:
WITH t as (
SELECT date_trunc('hour', NOW()) hour_t
)
INSERT INTO my_table(ID, TIME)
VALUES
(1, (select hour_t from t)),
(2, (select hour_t from t));

Related

What would be Postgres equivalent of this T-SQL

I want to run this either interactively from psql or from code.
create proc recent_orders_by_region
as
select top(3) * from view_orders where region = 'NA' order by order_date desc
select top(3) * from view_orders where region = 'WE' order by order_date desc
select top(3) * from view_orders where region = 'EE' order by order_date desc
You can use rank() over () but remember that order must be unique, otherwise RANK will assign the same number to multiple rows in group.
Query with example data
SELECT a.* FROM (
SELECT *,
rank() OVER (
PARTITION BY region
ORDER BY order_date DESC,ident DESC
)
FROM (
values
(1, current_date, 'NA'),(2, current_date-1, 'NA'),
(3, current_date-1, 'NA'),(4, current_date-4, 'NA'),
(5, current_date, 'NA1'),(6, current_date-1, 'NA1'),
(7, current_date-1, 'NA1'),(8, current_date-4, 'NA1')
) view_orders (ident, order_date, region)
) a WHERE RANK <=3
ident is only for demonstration purposes and should be replaced by real column from view_orders table

Is there a better way to do update PostgreSQL

I'm trying to update the ended_at and active columns in the test_subscription table when the max period_end has not passed.
I'm using the below query but I doubt it's the most idiomatic way. Any suggestions on improvements are very much welcome.
Creating the tables:
CREATE TABLE test_subscription (
id INTEGER PRIMARY key,
started_at timestamp,
ended_at TIMESTAMP,
active boolean
);
CREATE TABLE test_invoice (
id INTEGER PRIMARY key,
subscription_id INTEGER,
period_start timestamp,
period_end timestamp
);
INSERT INTO test_subscription (id, started_at, ended_at, active)
values(1, '2017-01-01 00:00:00', NULL, TRUE);
INSERT INTO test_subscription (id, started_at, ended_at, active)
values(2, '2017-01-01 00:00:00', NULL, TRUE);
INSERT INTO test_invoice (id, subscription_id, period_start, period_end)
values(1, 1, '2017-01-01 00:00:00', '2017-12-01 00:00:00');
INSERT INTO test_invoice (id, subscription_id, period_start, period_end)
values(2, 1, '2017-12-02 00:00:00', '2019-12-01 00:00:00');
INSERT INTO test_invoice (id, subscription_id, period_start, period_end)
values(3, 2, '2017-01-01 00:00:00', '2017-12-01 00:00:00');
I'm updating using the below.
UPDATE test_subscription
SET ended_at = (CASE WHEN (SELECT
MAX(period_end)
FROM test_invoice
WHERE test_subscription.id = test_invoice.subscription_id
) < now()
THEN (SELECT MAX(period_end)
FROM test_invoice
WHERE test_subscription.id = test_invoice.subscription_id
)
ELSE NULL
end),
active = (CASE WHEN (SELECT MAX(period_end)
FROM test_invoice
WHERE test_subscription.id = test_invoice.subscription_id
) < now()
THEN TRUE
ELSE FALSE
end);
Updates like that are usually faster if you first collect all the aggregates, then run the update using that intermediate result. Co-related sub-queries tend to be much slower.
update test_subscription s
set ended_at = case when t.latest_end < current_timestamp then t.latest_end end,
active = t.latest_end < current_timestamp
from (
select s.id,
max(i.period_end) as latest_end
from test_subscription s
join test_invoice i on s.id = i.subscription_id
group by s.id
) t
where t.id = s.id;
Online example: http://rextester.com/NMMF41667
You can put the MAX within CASE
UPDATE test_subscription s
SET ( ended_at, active ) = (SELECT MAX(CASE
WHEN period_end < NOW() THEN
period_end
END),
MAX(CASE
WHEN period_end < NOW() THEN 'TRUE'
ELSE 'FALSE'
END) :: BOOLEAN
FROM test_invoice i
WHERE s.id = i.subscription_id);
Demo

How can I add values to a column that show the ranking by a random value?

I can't see what is going wrong here:
DECLARE #cData TABLE(cID NVARCHAR(1), cSeed DECIMAL(8,8), cRank INT)
INSERT INTO #cData (cID, cSeed) SELECT 'W', RAND()
INSERT INTO #cData (cID, cSeed) SELECT 'X', RAND()
INSERT INTO #cData (cID, cSeed) SELECT 'Y', RAND()
INSERT INTO #cData (cID, cSeed) SELECT 'Z', RAND()
SELECT cID, cSeed, (RANK() OVER (ORDER BY cSeed)) AS cRank FROM #cData
UPDATE #cData
SET cRank = (SELECT (RANK() OVER (ORDER BY cSeed)))
SELECT * FROM #cData
Why am I getting different results from my first select statement than I am from my second--why didn't my update statement put the same data into the table that my first select statement displayed?
SELECT (RANK() OVER (ORDER BY cSeed))
This is a statement on its own, correlated by a column used only in OVER / ORDER BY clause.
It operates over an implied rowset of exactly one record (the current record from #cData) and hence always returns 1, as the rank of the only record in a set is 1 by definition.
I believe you want to run this instead:
WITH t AS
(
SELECT *,
RANK() OVER (ORDER BY cSeed) rnk
FROM #cData
)
UPDATE t
SET cRank = rnk

SQL Server : group by with corresponding row values

I need to write a T-SQL group by query for a table with multiple dates and seq columns:
DROP TABLE #temp
CREATE TABLE #temp(
id char(1),
dt DateTime,
seq int)
Insert into #temp values('A','2015-03-31 10:00:00',1)
Insert into #temp values('A','2015-08-31 10:00:00',2)
Insert into #temp values('A','2015-03-31 10:00:00',5)
Insert into #temp values('B','2015-09-01 10:00:00',1)
Insert into #temp values('B','2015-09-01 10:00:00',2)
I want the results to contains only the items A,B with their latest date and the corresponding seq number, like:
id MaxDate CorrespondentSeq
A 2015-08-31 10:00:00.000 2
B 2015-09-01 10:00:00.000 2
I am trying with (the obviously wrong!):
select id, max(dt) as MaxDate, max(seq) as CorrespondentSeq
from #temp
group by id
which returns:
id MaxDate CorrespondentSeq
A 2015-08-31 10:00:00.000 5 <-- 5 is wrong
B 2015-09-01 10:00:00.000 2
How can I achieve that?
EDIT
The dt datetime column has duplicated values (exactly same date!)
I am using SQL Server 2005
You can use a ranking subselect to get only the highest ranked entries for an id:
select id, dt, seq
from (
select id, dt, seq, rank() over (partition by id order by dt desc, seq desc) as r
from #temp
) ranked
where r=1;
SELECT ID, DT, SEQ
FROM (
SELECT ID, DT, SEQ, Row_Number()
OVER (PARTITION BY id ORDER BY dt DESC, seq DESC) AS row_number
FROM temp
) cte
WHERE row_number = 1;
Demo : http://www.sqlfiddle.com/#!3/3e3d5/5
With trial and errors maybe I have found a solution, but I'm not completely sure this is correct:
select A.id, B.dt, max(B.seq)
from (select id, max(dt) as maxDt
from #temp
group by id) as A
inner join #temp as B on A.id = B.id AND A.maxDt = B.dt
group by A.id, B.dt
Select id, dt, seq
From #temp t
where dt = (Select Max(dt) from #temp
Where id = t.Id)
If there are duplicate rows, then you also need to specify what the query processor should use to determine which of the duplicates to return. Say you want the lowest value of seq,
Then you could write:
Select id, dt, seq
From #temp t
where dt = (Select Max(dt) from #temp
Where id = t.Id)
and seq = (Select Min(Seq) from #temp
where id = t.Id
and dt = t.dt)

way to ignore intermediate column in an INSERT SELECT statement?

For a insert ... select statement, is there a way to ignore a variable that is required for the select statement but should be inserted?
I'm using a rank function to select which data to insert. Unfortunately the rank function can't be called in the where clause.
insert into target_table(v1
,v2
,v3
)
select v1
,v2
,v3
,RANK() over (partition by group_col
order by order_col desc
) as my_rank
from source_table
where my_rank > 1
The result is the following error:
Msg 121, Level 15, State 1, Line 1 The select list for the INSERT
statement contains more items than the insert list. The number of
SELECT values must match the number of INSERT columns.
I know I can do this using a temporary table but would like to keep it in a single statement if possible.
Wrap your main query inside a subquery.
INSERT INTO target_table
(v1, v2, v3)
SELECT q.v1, q.v2, q.v3
FROM (SELECT v1, v2, v3,
RANK() OVER (PARTITION BY group_col ORDER BY order_col DESC) AS my_rank
FROM source_table) q
WHERE q.my_rank > 1;
For SQL Server 2005+, you could also use a CTE:
WITH cteRank AS (
SELECT v1, v2, v3,
RANK() OVER (PARTITION BY group_col ORDER BY order_col DESC) AS my_rank
FROM source_table
)
INSERT INTO target_table
(v1, v2, v3)
SELECT v1, v2, v3
FROM cteRank
WHERE my_rank > 1;