What am I doing wrong here?
I want to write a function that returns a table of all the products that we have had dismal
performance for between two dates - where 'dismal' means marketing costs exceeded profits. I
have three tables: products, spend and transactions.
CREATE TABLE products
(id int not null, name varchar(255), primary key (id));
CREATE TABLE spend
(id int not null, spenddate date, spendamount decimal (9, 2));
CREATE TABLE transactions
(id int not null, transactiondate date, profit decimal (9, 2));
What I'd do is union queries between the two tables and then sum them to get a line per
product:
WITH a as (
SELECT products.id, products.name,
sum(transactions.profit) as profit,
sum(0) as spendamount
FROM transactions
WHERE transactiondate BETWEEN startdate AND enddate
GROUP BY products.id, products.name
UNION ALL
SELECT id, sum(0) as profit
sum(amount) as spendamount
FROM spend
WHERE spenddate BETWEEN startdate AND enddate
GROUP BY id)
SELECT products.id, products.name, sum(profit)-sum(spendamount) as loss
FROM a, products
WHERE products.id = a.id
GROUP BY products.id, products.name
HAVING sum(profit)-sum(spendamount) < 0;
But I don't want to keep changing the start and enddate values every time I run my code.
I thought I'd do this:
CREATE FUNCTION report_dismal_products (startdate date, enddate date,
out id int,
out name varchar(255),
out loss decimal(9, 2)
)
RETURNS SETOF RECORD
as $$
SELECT products.id, products.name, sum(profit)-sum(spendamount) as loss
FROM
(
SELECT id,
sum(transactions.profit) as profit,
sum(0) as spendamount
FROM transactions
WHERE transactiondate BETWEEN startdate AND enddate
GROUP BY id
UNION ALL
SELECT id, sum(0) as profit
sum(amount) as spendamount
FROM spend
WHERE spenddate BETWEEN startdate AND enddate
GROUP BY id) a, products
WHERE products.id = a.id
GROUP BY products.id, products.name
HAVING sum(profit)-sum(spendamount) < 0;
$$ Language sql;
But it returns
ERROR: column "startdate" does not exist
LINE 17: WHERE transactiondate BETWEEN startdate AND enddate
Related
I want to get the min(id) from a table based on the date inserted, as not always the min(id) will be the same as the id of the min(date_inserted). I can do it with subquery but I need to do it without the subquery as it is part of a bigger SQL.
This is what I got so far:
create table tbl(
id int ,
userid int,
dt timestamp)
SELECT id
FROM (
-- if I could do min(id order by dt) that would solve the issue
select id, row_number() over(partition by userid order by dt) row_number
from table
where userid = :userid
) withs
WHERE row_number=1
I am trying to track payments made by customers using the following tables:
CREATE TABLE IF NOT EXISTS invoices
(
id integer NOT NULL DEFAULT nextval('invoices_id_seq'::regclass),
customer_id integer NOT NULL
)
CREATE TABLE IF NOT EXISTS invoice_entries
(
id integer NOT NULL DEFAULT nextval('invoice_entries_id_seq'::regclass),
price numeric,
invoice_id integer NOT NULL
)
CREATE TABLE IF NOT EXISTS customer_transactions
(
id integer NOT NULL DEFAULT nextval('customer_transactions_id_seq'::regclass),
total_amount numeric NOT NULL,
invoice_id integer,
customer_id integer NOT NULL,
)
My problem is that a payment can be made to the customer's account, OR, it could be made to a specific invoice.
My goal is to generate aggregated columns that show the running balance, as well as the amount paid on any given invoice. If the payment was made directly to the customer's account, it will apply to any remaining balances on each invoice.
So far I have gotten this far:
WITH RECURSIVE CTE_R AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY ct.customer_id) AS row_id,
pb.invoice_balance,
pb.invoice_id,
ct.total_amount
FROM customer_invoice_balances() as pb
LEFT JOIN customer_transactions as ct ON ct.customer_id = pb.customer_id
),
CTE AS (
SELECT
row_id,
invoice_balance,
invoice_id,
total_amount,
invoice_balance AS remaining_balance,
0::numeric AS amount_paid
FROM CTE_R
WHERE row_id = 1
UNION ALL
SELECT
cr.row_id,
cr.invoice_balance,
cr.invoice_id,
cr.total_amount,
CASE
WHEN cr.invoice_id = c.invoice_id THEN remaining_balance - cr.total_amount
ELSE remaining_balance
END,
CASE
WHEN cr.invoice_id = c.invoice_id THEN amount_paid + cr.total_amount
ELSE amount_paid
END
FROM
CTE c, CTE_R cr
WHERE c.row_id = cr.row_id -1
)
SELECT * FROM CTE
At this point I am stumped, because the remaining balance will go negative and not move to the next invoice that has a balance. Am I going about this the right way?
Edit: The other problem I am having is that the recursive CTE is very inefficient, and the query takes far too long.
I want to use order by clause in my last sql query and I have more than 3 union queries. I do not want to order the top 2 union query but I want to use order by clause in my last sql statement.
Currently, getting error
ORDER BY items must appear in the select list if the statement contains a UNION, INTERSECT or EXCEPT operator.
select 'Total Number of pat' Name, convert(varchar(20), count(id)) Number from table2 where id = 5
union
select 'Total Number of Doc' Name, convert(varchar(20), count(id)) Number from table3
union
select x.usertype, count(distinct userid) cnt
from [dbo].table1 t
cross apply (values (
case when t.userid like '%[0-9][0-9[0-9]' then 'transition' else 'non transition' end,
t.userid
)) x(usertype, userid)
where t.date >= dateadd(day,-7, getdate())
group by x.usertype
order by usertype desc
order by is sorting the result of the unions all together however you can introduce a orderIndex column for imlementing the right ordering.
Here the sample:
I've tried to build sample data in the following code.
create table table1(
userid varchar(100),
usertype varchar(100),
date date
)
insert into table1(userid, date) values ('Einsmayr', '2020-10-27')
insert into table1(userid, date) values ('Eins123', '2020-10-27')
insert into table1(userid, date) values ('Einschmid', '2020-10-27')
insert into table1(userid, date) values ('Einshuber', '2020-10-27')
insert into table1(userid, date) values ('Einsreitmayr', '2020-10-27')
create table table2 (
Name varchar(100),
id int
)
insert into table2(Name, id) values('Zweirich', 5)
insert into table2(Name, id) values('Zweifel', 6)
create table table3 (
Name varchar(100),
id int
)
insert into table3(Name, id) values('Dreisinger', 17)
insert into table3(Name, id) values('Dreibert', 18)
This allows the following queries:
select usertype, Number
from (
select 'Total Number of pat' usertype, convert(varchar(20), count(id)) Number, 1 orderIndex from table2 where id = 5
union
select 'Total Number of Doc' Name, convert(varchar(20), count(id)) Number, 2 orderIndex from table3
union
select usertype, count(distinct userid) Number, 3 orderIndex
from (
select userid, case when userid like '%[0-9][0-9[0-9]' then 'transition' else 'non transition' end usertype
from table1
where date >= dateadd(day,-7, getdate())
) x
group by x.usertype
) y
order by y.orderIndex, y.usertype
Find the solution here: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ac396c48f5dbcb4a53ad40fac70e9236
I have the following query
SELECT a.account_id, sum(p.amount) AS amount
FROM accounts a
LEFT JOIN users_accounts ua
JOIN users u
JOIN payments p on p.meta_id = u.user_id
ON u.user_id = ua.user_id
ON ua.account_id = a.account_id
WHERE p.date_prcsd BETWEEN '2017-08-01 00:00:00' AND '2017-08-31 23:59:59'
GROUP BY a.account_id
ORDER BY account_id ASC;
What I want is all the rows from accounts a and zeroes for missing amount data. Same result set for different types of joins and different join structures - only rows that have some payments in p.
Where do I go wrong?
Simplified:
SELECT a.account_id
,sum(coalesce(p2.amount, 0)) AS amount
FROM accounts a
LEFT JOIN users_accounts ua ON (a.account_id = ua.account_id)
LEFT JOIN users u ON (ua.user_id = u.user_id)
LEFT JOIN (
SELECT p.meta_id
,p.amount
FROM payments p
WHERE p.date BETWEEN '2017-08-01' AND '2017-08-10'
) AS p2 ON (u.user_id = p2.meta_id)
GROUP BY a.account_id
ORDER BY account_id ASC;
Result:
account_id | amount
------------+--------
1 | 4
2 | 0
3 | 0
(3 rows)
Explanation: you need to take care of all returning null values. coalesce() does that for you. The where-clause is actually the real problem in your solution because it filters out rows that you would want to have in your endresult. On top of that: you left out the left join for the other tables. I created a simplified test db:
$ cat tables.sql
drop table users_accounts;
drop table payments;
drop table users;
drop table accounts;
create table accounts (account_id serial primary key, name varchar not
null);
create table users (user_id serial primary key, name varchar not null);
create table users_accounts(user_id int references users(user_id),
account_id int references
accounts(account_id));
create table payments(meta_id int references users(user_id), amount int
not null, date date);
insert into accounts (account_id, name) values (1, 'Account A'), (2,
'Account B'), (3, 'Account C');
insert into users (user_id, name) values (1, 'Marc'), (2, 'Ruben'), (3,
'Isaak');
insert into users_accounts (user_id, account_id) values (1,1),(2,1);
insert into payments(meta_id, amount, date) values (1,1, '2017-08-01'),
(1,2, '2017-08-11'),(1,3, '2017-08-03'),(2,1, null),(2,2, null),(2,3,
null);
I have a table (in SQL Server 2005) of daily weather data for a single location which includes these columns:
LogDate DATETIME
HighTemp INT
Temp6MonthHighAverage INT
LogDate and HighTemp have data. HighTemp6MonthAverage will be populated with, as the name suggests, the average high temperature for the 6 months ending in LogDate.
There are similar requirements for LowTemp, as well as humidity and several other items, for data spanning decades.
I find myself thinking in circles. Can I derive this average for each row in an UPDATE statement using set operations, or do I need to implement a solution with cursors? I will appreciate any suggestions.
-- select
select HighTemp, LogDate,(select AVG(HighTemp)
from tbl where
DATEDIFF(MONTH, LogDate, t1.LogDate) between 0 and 6)
from tbl t1
-- update
update t1 set Temp6MonthHighAverage = (select AVG(HighTemp)
from tbl where
DATEDIFF(MONTH, LogDate, t1.LogDate) between 0 and 6)
from tbl t1
You can certainly do this with a simple UPDATE:
UPDATE table SET Temp6MonthHighAverage =
(SELECT AVG(HighTemp) FROM table t2 WHERE
t2.LogDate <= table.LogDate
AND t2.LogDate > DATEADD(m, -6, table.LogDate)
)
To avoid re-calculating constantly (since the past will not change), just add a WHERE Temp6MonthHighAverage IS NULL at the end and the same UPDATE can be run as needed to fill in the gaps as new dates are added.
Have a look at something like this
DECLARE #Table TABLE(
LogDate DATETIME,
HighTemp INT,
Temp6MonthHighAverage INT
)
INSERT INTO #Table SELECT '01 Jan 2000', 15, NULL
INSERT INTO #Table SELECT '01 May 2000', 14, NULL
INSERT INTO #Table SELECT '01 Jun 2000', 13, NULL
INSERT INTO #Table SELECT '01 Jul 2000', 12, NULL
INSERT INTO #Table SELECT '01 Dec 2000', 17, NULL
SELECT *
FROM #Table
;WITH DistinctDates AS (
SELECT DATEADD(month,-6,LogDate) StartDate,
LogDate EndDate,
HighTemp
FROM #Table
)
, Aggregates AS (
SELECT dd.EndDate LogDate,
dd.HighTemp,
MAX(t.HighTemp) Temp6MonthHighAverage
FROM DistinctDates dd LEFT JOIN
#Table t ON t.LogDate BETWEEN dd.StartDate AND dd.EndDate
GROUP BY dd.EndDate,
dd.HighTemp
)
UPDATE #Table
SET Temp6MonthHighAverage = a.Temp6MonthHighAverage
FROM #Table t INNER JOIN
Aggregates a ON t.LogDate = a.LogDate
SELECT *
FROM #Table