SQL Display details and count fields through checks - sql

I have created an Oracle database which contains information regarding renting flats/apartments. I am trying to create a SQL script which will, first, count how many students have not paid their first invoice (so there should be an "is not null" check on this field) and, second, display their details.
Here is an example of the data the table is called INVOICE:
+-----------+-------+---------------+---------------+----------------+--------+---------------+-------------+
| INVOICEID | PRICE | PAYMENTMETHOD | FIRSTREMINDER | SECONDREMINDER | RENTID | PAYMENTSTATUS | DATESENT |
+-----------+-------+---------------+---------------+----------------+--------+---------------+-------------+
| 1 | 415 | Visa | 10/FEB/2016 | - | 1 | Paid | 15/MAR/2016 |
| 2 | 600 | Cash | 15/FEB/2016 | - | 2 | Unpaid | 12/MAR/2016 |
| 3 | 750 | Visa | 10/FEB/2016 | 15/MAR/2016 | 1 | Paid | 15/MAR/2016 |
+-----------+-------+---------------+---------------+----------------+--------+---------------+-------------+
Using this data, the SQL statement should return the details for number 2 in the table and count them.
I'd be grateful if anyone could help with this because I have no clue where to start. SQL is not my main programming language.

It seems this should suffice...
To get the count:
select count(*) from invoice where paymentstatus = 'Unpaid'
To get the details for those rows:
select * from invoice where paymentstatus = 'Unpaid'
You mentioned something about an "is not null" check "on this field" - what field are you referring to? Isn't the paymentstatus column sufficient?

The query below enumerates the invoice #'s per RENTID and allows you to select 1st invoices that were unpaid:
select * from (
select *,
row_number() over (partition by rentid order by invoiceid) invoice_number
from mytable
) t where invoice_number = 1 and paymentstatus = 'Unpaid'
If you just want the count, then replace select * with select count(*)
select count(*) from (
select *,
row_number() over (partition by rentid order by invoiceid) invoice_number
from mytable
) t where invoice_number = 1 and paymentstatus = 'Unpaid'
Another way that checks whether secondreminder is null to determine whether an invoice is a 1st invoice
select count(*)
from mytable
where secondreminder is null
and paymentstatus = 'Unpaid'

Related

How to create BigQuery this query in retail dataset

I have a table with user retail transactions. It includes sales and cancels. If Qty is positive - it sells, if negative - cancels. I want to attach cancels to the most appropriate sell. So, I have tables likes that:
| CustomerId | StockId | Qty | Date |
|--------------+-----------+-------+------------|
| 1 | 100 | 50 | 2020-01-01 |
| 1 | 100 | -10 | 2020-01-10 |
| 1 | 100 | 60 | 2020-02-10 |
| 1 | 100 | -20 | 2020-02-10 |
| 1 | 100 | 200 | 2020-03-01 |
| 1 | 100 | 10 | 2020-03-05 |
| 1 | 100 | -90 | 2020-03-10 |
User with ID 1 has the following actions: buy 50 -> return 10 -> buy 60 -> return 20 -> buy 200 -> buy 10 - return 90. For each cancel row (with negative Qty) I find the previous row (by Date) with positive Qty and greater than cancel Qty.
So I need to create BigQuery queries to create table likes this:
| CustomerId | StockId | Qty | Date | CancelQty |
|--------------+-----------+-------+------------+-------------|
| 1 | 100 | 50 | 2020-01-01 | -10 |
| 1 | 100 | 60 | 2020-02-10 | -20 |
| 1 | 100 | 200 | 2020-03-01 | -90 |
| 1 | 100 | 10 | 2020-03-05 | 0 |
Does anybody help me with these queries? I have created one candidate query (split cancel and sales, join them, and do some staff for removing), but it works incorrectly in the above case.
I use BigQuery, so any BQ SQL features could be applied.
Any ideas will be helpful.
You can use the following query.
;WITH result AS (
select t1.*,t2.Qty as cQty,t2.Date as Date_t2 from
(select *,ROW_NUMBER() OVER (ORDER BY qty DESC) AS [ROW NUMBER] from Test) t1
join
(select *,ROW_NUMBER() OVER (ORDER BY qty) AS [ROW NUMBER] from Test) t2
on t1.[ROW NUMBER] = t2.[ROW NUMBER]
)
select CustomerId,StockId,Qty,Date,ISNULL(cQty, 0) As CancelQty,Date_t2
from (select CustomerId,StockId,Qty,Date,case
when cQty < 0 then cQty
else NULL
end AS cQty,
case
when cQty < 0 then Date_t2
else NULL
end AS Date_t2 from result) t
where qty > 0
order by cQty desc
result: https://dbfiddle.uk
You can do this as a gaps-and-islands problem. Basically, add a grouping column to the rows based on a cumulative reverse count of negative values. Then within each group, choose the first row where the sum is positive. So:
select t.* (except cancelqty, grp),
(case when min(case when cancelqty + qty >= 0 then date end) over (partition by customerid grp) = date
then cancelqty
else 0
end) as cancelqty
from (select t.*,
min(cancelqty) over (partition by customerid, grp) as cancelqty
from (select t.*,
countif(qty < 0) over (partition by customerid order by date desc) as grp
from transactions t
) t
from t
) t;
Note: This works for the data you have provided. However, there may be complicated scenarios where this does not work. In fact, I don't think there is a simple optimal solution assuming that the returns are not connected to the original sales. I would suggest that you fix the data model so you record where the returns come from.
The below query seems to satisfy the conditions and the output mentioned.The solution is based on mapping the base table (t) and having the corresponding canceled qty row alongside from same table(t1)
First, a self join based on the customer and StockId is done since they need to correspond to the same customer and product.
Additionally, we are bringing in the canceled transactions t1 that happened after the base row in table t t.Dt<=t1.Dt and to ensure this is a negative qty t1.Qty<0 clause is added
Further we cannot attribute the canceled qty if they are less than the Original Qty. Therefore I am checking if the positive is greater than the canceled qty. This is done by adding a '-' sign to the cancel qty so that they can be compared easily. -(t1.Qty)<=t.Qty
After the Join, we are interested only in the positive qty, so adding a where clause to filter the other rows from the base table t with canceled quantities t.Qty>0.
Now we have the table joined to every other canceled qty row which is less than the transaction date. For example, the Qty 50 can have all the canceled qty mapped to it but we are interested only in the immediate one came after. So we first group all the base quantity values and then choose the date of the canceled Qty that came in first in the Having clause condition HAVING IFNULL(t1.dt, '0')=MIN(IFNULL(t1.dt, '0'))
Finally we get the rows we need and we can exclude the last column if required using an outer select query
SELECT t.CustomerId,t.StockId,t.Qty,t.Dt,IFNULL(t1.Qty, 0) CancelQty
,t1.dt dt_t1
FROM tbl t
LEFT JOIN tbl t1 ON t.CustomerId=t1.CustomerId AND
t.StockId=t1.StockId
AND t.Dt<=t1.Dt AND t1.Qty<0 AND -(t1.Qty)<=t.Qty
WHERE t.Qty>0
GROUP BY 1,2,3,4
HAVING IFNULL(t1.dt, '0')=MIN(IFNULL(t1.dt, '0'))
ORDER BY 1,2,4,3
fiddle
Consider below approach
with sales as (
select * from `project.dataset.table` where Qty > 0
), cancels as (
select * from `project.dataset.table` where Qty < 0
)
select any_value(s).*,
ifnull(array_agg(c.Qty order by c.Date limit 1)[offset(0)], 0) as CancelQty
from sales s
left join cancels c
on s.CustomerId = c.CustomerId
and s.StockId = c.StockId
and s.Date <= c.Date
and s.Qty > abs(c.Qty)
group by format('%t', s)
if applied to sample data in your question - output is

change and merge multiple values on column if there is duplicate value in other columns

Sorry, I'm new to SQL..I'm using Sybase Central
I need help, I'm currently stuck on data table from select statement (stored procedure) like this :
select case when product like 'A%' then 'Product A' when product like 'B%'
then 'Product B' else 'Product C' end as
'ProductGroup',product,invoice,customer from data_sales
group by product,invoice,customer
Results :
+--------------+---------+---------+---------+
| ProductGroup | Product | Invoice | Customer|
+--------------+---------+---------+---------+
| Product A | A1 | INV001 | MR.A |
| Product A | A1 | INV002 | MR.B |
| Product B | B1 | INV002 | MR.B |
| Product B | B1 | INV003 | MR.C |
+--------------+---------+---------+---------+
I want to merge and change the value into Product C in ProductGroup column, if there is duplicate values on Invoices or Customers columns
Results should be like this :
+--------------+--------+---------+
| ProductGroup | Invoice| Customer|
+--------------+--------+---------+
| Product A | INV001 | MR.A |
| Product C | INV002 | MR.B |
| Product B | INV003 | MR.C |
+--------------+--------+---------+
I've been using case when and group by method, but it's still showing duplicate results
Any help would be really appreciated
Thank you
The below query does check the invoices or customers for duplicates done via group by. Grouping invoices will group the repeated invoices and will give the aggregate count as greater than 1 if found repeated else 0 similarly in or condition for customers. Is this what you want ?
Update Table
Set Product="Product C" where
Invoice In
(Select Invoice
from table group by
Invoice having
count(*)>1) or
Customers In (Select Customers
from table group by
Customers having
count(*)>1)
You can check the duplicates by having count(*)>1 clause with group by
update tab t
set ProductGroup = 'Product C'
where exists (
select 1
from tab tt
where exists ( select Invoice
from tab
where Invoice = tt.Invoice
group by Invoice
having count(*)>1 )
or exists ( select Customer
from tab
where Customer = tt.Customer
group by Customer
having count(*)>1 ) )
If you just want a query which returns the requested information (so no update):
select distinct CASE WHEN (select count()
from Tab t2
where t2.Customer = t1.Customer or t2.Invoice = t1.Invoice) > 1
THEN 'Product C'
ELSE ProductGroup
END "ProductGroup", Invoice, Customer
from Tab t1

Select only 1 payment from a table with customers with multiple payments

I have a table called "payments" where I store all the payments of my costumers and I need to do a select to calculate the non-payment rate in a given month.
The costumers can have multiples payments in that month, but I should count him only once: 1 if any of the payments is done and 0 if any of the payment was made.
Example:
+----+------------+--------+
| ID | DATEDUE | AMOUNT |
+----+------------+--------+
| 1 | 2016-11-01 | 0 |
| 1 | 2016-11-15 | 20.00 |
| 2 | 2016-11-10 | 0 |
+----+------------+--------+
The result I expect is from the rate of november:
+----+------------+--------+
| ID | DATEDUE | AMOUNT |
+----+------------+--------+
| 1 | 2016-11-15 | 20.00 |
| 2 | 2016-11-10 | 0 |
+----+------------+--------+
So the rate will be 50%.
But if the select is:
SELECT * FROM payment WHERE DATEDUE BETWEEN '2016-11-01' AND '2016-11-30'
It will return me 3 rows and the rate will be 66%, witch is wrong. Ideas?
PS: This is a simpler example of the real table. The real query have a lot of columns, subselects, etc.
It sounds like you need to partition your results per customer.
SELECT TOP 1 WITH TIES
ID,
DATEDUE,
AMOUNT
ORDER BY ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC)
WHERE DATEDUE BETWEEN '2016-11-01' AND '2016-11-30'
PS: The BETWEEN operator is frowned upon by some people. For clarity it might be better to avoid it:
What do BETWEEN and the devil have in common?
Try this
SELECT
id
, SUM(AMOUNT) AS AMOUNT
FROM
Payment
GROUP BY
id;
This might help if you want other columns.
WITH cte (
SELECT
id
, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY AMOUNT DESC ) AS RowNum
-- other row
)
SELECT *
FROM
cte
WHERE
RowNum = 1;
To calculate the rate, you can use explicit division:
select 1 - count(distinct case when amount > 0 then id end) / count(*)
from payment
where . . .;
Or, in a way that is perhaps easier to follow:
select avg(flag * 1.0)
from (select id, (case when max(amount) > 0 then 0 else 1 end) as flag
from payment
where . . .
group by id
) i

Return rows if data exists based on a given priority in SQL

I am trying to form a PostgreSQL statement that returns a customer email based on the email type with a given priority. Below I have a table with customers 1 and two. Customer 1 has both personal and company emails whereas customer 2 has on the company.
The problem I am trying to solve is returned the customers personal email if it exists first and if not return the company. So, the personal email is given priority over the company. Is this even possible in PostgreSQL?
customers
+------------+
| cusomterID |
+------------+
| 1 |
| 2 |
+------------+
customer_email
+------------+-------------+
| cusomterID | email_type |
+------------+-------------+
| 1 | personal | -- 0
| 2 | company | -- 1
| 1 | company | -- 1
+------------+-------------+
What I am trying now is not really working. It returns all of the rows and does not filter
SELECT *
FROM customers cs
JOIN cumstomer_email cm ON cm.customerId = cs.customreId
WHERE COALESCE(cm.email_type,0) IN (0,1)
One option would be to use conditional aggregation:
select customerId, max(case when email_type = 'personal' then email_type
else email_type
end) email_type
from customer_email
group by customerId
SQL Fiddle Demo
And here's another option using row_number():
select customerId, email_type
from (select *,
row_number() over (partition by customerId
order by email_type = 'personal' desc) rn
from customer_email) t
where rn = 1
More Fiddle
You could do this with a common table expression (CTE):
with emailPriority as (
select customerId,
max(email_type) emailType
from customer_email
group by customer_id)
select cs.*, cm.email_address
from customers cs join emailPriority ep on cs.customerId = ep.customerId
join customer_email cm on cm.email_type = ep.email_type

SQL join from the same table

How do I get grand total for orders made by credit card only from such a table :
order_id | meta_name | meta_value
___________________________________
1 | type | credit
1 | total | 1030.00
...
2 | type | check
2 | total | 930.00
..
3 | type | credit
3 | total | 330.00
what is the best way to describe such select operation if you are to search the Internet for a solution to this problem.
suppose I am MySQL.
You can do this with either join or group by. Here is the join method:
select ttype.order_id, cast(ttotal.meta_value as money)
from table ttype join
table ttotal
on ttype.order_id = ttotal.order_id and
ttype.meta_name = 'type' and
ttype.meta_value = 'credit' and
ttotal.meta_name = 'total';
If yo could have more than one total for an order, then you would still want to aggregate:
select ttype.order_id, sum(cast(ttotal.meta_value as money))
from table ttype join
table ttotal
on ttype.order_id = ttotal.order_id and
ttype.meta_name = 'type' and
ttype.meta_value = 'credit' and
ttotal.meta_name = 'total'
group by ttype.order_id