Using COUNT and UNION to extract data and support a scenario - sql

I am dealing with a database with thousands of customers.
I am wanting to find groups of single customers who have exactly ONE qualifying discount voucher which is valid and exactly ONE non-qualifying voucher which is valid.
A qualifying voucher is one that has a minimum spend amount of £0.01 or more.
A non-qualifying voucher is one that does not have a minimum spend and is therefore £0.00
'Valid' refers to the 'from' date being today or before and the 'to' date being today or in the future
I have initially set up the query below but all this is doing is searching for all customers who have valid qualifying AND non-qualifying voucherS. I am trying to find customers who have JUST ONE valid qualifying voucher and JUST ONE non-qualifying voucher:
select CustomerId, VoucherId, MinimumSpendAmount, ValidFromDate, ValidToDate
from dbo.discountvoucher
where ValidFromDate <= 15/11/2013
and ValidToDate >= 15/11/2013
order by CustomerId
I think I need to split this into 2 separate SELECT statements, one looking for single customers with 1 qualifying voucher (using COUNT), and one looking for single customers with 1 non-qualifying voucher (using COUNT). And then combining them with a UNION. But I could be totally wrong...
Please can anybody help

You can use a sub select with a GROUP BY and HAVING CLAUSE to find the customers that match your criteria.
select CustomerId, VoucherId, MinimumSpendAmount, ValidFromDate, ValidToDate
from dbo.discountvoucher
where ValidFromDate <= 15/11/2013
and ValidToDate >= 15/11/2013
and CustomerId in
(select CustomerId
from dbo.discountvoucher
where ValidFromDate <= 15/11/2013
and ValidToDate >= 15/11/2013
group by CustomerId
having sum(case when MinimumSpendAmount > 0 then 1 else 0 end) = 1
and sum(case when MinimumSpendAmount = 0 then 1 else 0 end) = 1
)
order by CustomerId

Related

Window function or Recursive query in Redshift

I try to classify customer based on monthly sales. Logic for calculation:
new customer – it has sales and appears for first time or has sales and returned after being lost (after 4 month period, based on sales_date)
active customer - not new and not lost.
lost customer - no sales and period (based on sales_date) more than 4 months
This is the desired output I'm trying to achieve:
The below Window function in Redshift classify however it is not correct.
It classified lost when difference between month > 4 in one row, however it did not classify lost if it was previous lost and revenue 0 until new status appear. How it can be updated?
with customer_status (
select customer_id,customer_name,sales_date,sum(sales_amount) as sales_amount_calc,
nvl(DATEDIFF(month, LAG(reporting_date) OVER (PARTITION BY customer_id ORDER by sales_date ASC), sales_date),0) AS months_between_sales
from customer
GROUP BY customer_id,customer_name,sales_date
)
select *,
case
WHEN months_between_sales = 0 THEN 'New'
WHEN months_between_sales > 0 THEN 'Active'
WHEN months_between_sales > 0 AND months_between_sales <= 4 and sales_amount_calc = 0 THEN 'Active'
WHEN /* months_between_sales > 0 and */ months_between_sales > 4 and sales_amount_calc = 0 THEN 'Lost'
ELSE 'Unknown'
END AS status
from customer_status
One way to solve to get cumulative sales amount partitioned on subset of potentially lost customers ( sales_amount = 0).
Cumulative amount for the customer partitioned
sum(months_between_sales) over (PARTITION BY customer_id ORDER by sales_date ASC rows unbounded preceding) as cumulative_amount,
How can it be changed to get sub-partitioned, for example when sales amount= 0 , in order to get lost correctly?
Does someone have ideas to translate the above logic into an
recursive query in Redshift?
Thank you

how to query a table date against a series of dates on another table

I have two tables, INVOICES and INV_PRICES. I am trying to find the Invoice table's part price from the Inv_Prices based upon the Invoice_Dt on the Invoice table; if the Invoice_Dt is between (greater than, but less than) or greater than the max EFF_DT on the Inv_Prices, then return that part's price.
I have tired variations on the following code, but no luck. I either do not get all the parts or multiple records.
SELECT DISTINCT A.INVOICE_NBR, A.INVOICE_DT, A.PART_NO,
CASE WHEN TRUNC(A.INVOICE_DT) >= TRUNC(B.EFF_DT) THEN B.DLR_NET_PRC_AM
WHEN (TRUNC(A.INVOICE_DT)||ROWNUM >= TRUNC(B.EFF_DT)||ROWNUM) AND (TRUNC(B.EFF_DT)||ROWNUM <= TRUNC(A.INVOICE_DT)||ROWNUM) THEN B.DLR_NET_PRC_AM
/*MAX(B.EFF_DT) THEN B.DLR_NET_PRC_AM*/
ELSE 0
END AS PRICE
FROM INVOICES A,
INV_PRICES B
WHERE A.PART_NO = B.PART_NO
ORDER BY A.INVOICE_NBR
Can someone assist? I have a sample of each table if needed.
Doesn't it work to put the condition in the JOIN conditions? You can calculate the period when a price is valid using LEAD():
SELECT i.INVOICE_NBR, i.INVOICE_DT, i.PART_NO,
COALESCE(ip.DLR_NET_PRC_AM, 0) as price
FROM INVOICES i LEFT JOIN
(SELECT ip.*, LEAD(eff_dt) OVER (PARTITION BY PART_NO ORDER BY eff_dt) as next_eff_dt
FROM INV_PRICES ip
) ip
ON i.PART_NO = ip.PART_NO AND
i.invoice_dt >= ip.eff_dt AND
(i.invoice_dt < ip.next_eff_dt or ip.next_eff_dt is null)
ORDER BY i.INVOICE_NBR

SQL Server date range query not working as expected

I wrote a query for return orders received for a certain date range. The goal is to find orders made in a few hours for online and store. However, the order return ORDER_IDs that are outside the date range. Here is my query.
SELECT
ORDER_ID
FROM
ORDERS
WHERE
TRANSACTION_TYPE = 'ONLINE' OR TRANSACTION_TYPE = 'STORE'
AND ORDER_COUNT > 0
AND (ORDER_DATE >= '2017-11-20 02:04:25.247'
AND ORDER_DATE< '2017-11-20 02:06:40.887')
Now when I run a query with one of the ORDER_ID returned, it returns a date range outside the lower date value.
SELECT ORDER_DATE FROM ORDERS WHERE ORDER_ID=104
ORDER_DATE
-----------
2017-11-24 10:10:14.520
This clearly is incorrect. Why is it returning a date greater than 2017-11-20 when the date range is supposed to be between 2017-11-20 02:04:25.247 and 2017-11-20 02:06:40.887?
How can I fix the query to find orders a few hours apart, for example orders made on the same day between 3pm and 8pm?
When I tried the suggestion I get no rows, even though there are rows in the date range.
ORDER_ID ORDER_DATE TRANSACTION_TYPE
433 2017-11-20 02:04:25.247 ONLINE
448 2017-11-20 02:06:40.887 ONLINE
your WHERE condition is wrong. Maybe should be like this
WHERE ( -- added this
TRANSACTION_TYPE = 'ONLINE'
OR TRANSACTION_TYPE = 'STORE'
) -- added this
AND ORDER_COUNT > 0
AND (
ORDER_DATE >= '2017-11-20 02:04:25.247'
AND ORDER_DATE < '2017-11-20 02:06:40.887'
)
you missed out some parenthesis ( ) somewhere
This worked:
WHERE(
TRANSACTION_TYPE = 'ONLINE'
OR
TRANSACTION_TYPE = 'STORE'
)
AND ORDER_COUNT > 0
AND
(
ORDER_DATE <CURRENT_TIMESTAMP
AND
ORDER_DATE > DATEADD(hh,-3,CURRENT_TIMESTAMP)
)
You've mixed AND's and OR's together. One solution is to use IN for TRANSACTION_TYPE instead of two statements with OR operator. This avoids mixing of AND and OR.
SELECT ORDER_ID
FROM ORDERS
WHERE TRANSACTION_TYPE IN ('ONLINE', 'STORE')
AND ORDER_COUNT>0
AND ORDER_DATE >= '2017-11-20 02:04:25.247'
AND ORDER_DATE< '2017-11-20 02:06:40.887'
Another possibility is to use parenthesis to group the OR statements.

SQL count records depending on criteria

I have a table which has account information for each individual working day. Each record shows the balance on the account for that particular day. I need to create a field that has a running count on how many days it has been since the balance on that particular account was zero.
For example: I have an account which is number 00000001. It was opened a week ago. The database has created a record for the account for last Tuesday, Wednesday, Thursday, Friday and Monday. The account did not have a balance until Friday and the balance was the same for Monday. I want the field to show '2' as a result. Further to this if the balance drops to '0' today, I need the count to reset on the next record and start again if the account has a balance the following day. There are several accounts in this table so I need this to work for each one individually.
Below is as far as I have got:
SELECT
pos.EffDate,
cus.CNA1,
pos.ACNO,
pos.CCY,
pos.LDBL,
pos.LDBLUSD,
MIN(pos3.effdate) AS 'First Post Date',
pos3.Balcount
FROM[dbo].[Account] AS pos
JOIN [dbo].[Customer] AS cus ON ((pos.CNUM=cus.CUST_NO) AND (cus.effdate=pos.effdate))
LEFT JOIN (SELECT pos2.effdate, pos2.ACNO, SUM(CASE pos2.LDBL WHEN 0 THEN 0 ELSE 1 END) AS 'Balcount' FROM [dbo].[Account] AS pos2 GROUP BY pos2.ACNO, pos2.Effdate HAVING pos2.effdate BETWEEN pos2.effdate AND MIN(pos2.effdate)) pos3 ON pos3.ACNO = pos.ACNO
WHERE pos.effdate >='1 Dec 2015' AND pos.Effdate <'30 Dec 2015'
AND pos.srcsys = 'MP_UK'
AND pos.LDBL <= 0
AND pos.CNUM <> '000020'
AND pos.intbear <> 'N'
AND pos.blockdeb IS null
GROUP BY pos.EffDate,cus.CNA1,pos.ACNO,pos.CCY,pos.LDBL,pos.LDBLUSD, pos3.Balcount
ORDER BY 1,2,3
If you need me to clarify anything please let me know. All help is greatly appreciated.
Thanks,
Ben
Basically, you want to group the data, based on the number of time the account is zero before a given row. Then, within each group, you want to enumerate the records.
In SQL Server 2012+, you can do these things with window functions. I am not sure exactly what your sample query has to do with the question, but here is the basic idea:
select a.*, row_number() over (partition by cust_no, grp order by eff_date) as seqnum
from (select a.*,
sum(case when balance = 0 then 1 else 0 end) over
(partition by cust_no order by eff_date) as grp
from Account a
) a;
In earlier versions of SQL Server, you can do something very similar using apply.

Join two Queries so that the second query becomes a row in the results of query 1

I have two queries that I would like to combine so i can make a chart out of the results.
The results have to be very specific or the chart will not display the information properly
I am using MS SQL in Crystal Reports 11
Below is the results I am looking for.
Date Invoice Type Amount
2012/08 Customer Payment 500
2012/08 Customer Invoice 1000
2012/08 Moving Balance 1500
2012/09 Customer Invoice 400
2012/09 Moving Balance 1900
2012/10 Interest 50
2012/10 Moving Balance 1950
So the First query returns the following results
Date Invoice Type Amount
2012/08 Customer Payment 500
2012/08 Customer Invoice 1000
2012/09 Customer Invoice 400
2012/10 Interest 50
and the second query returns
Date Invoice Type Amount
2012/08 Moving Balance 1500
2012/09 Moving Balance 1900
2012/10 Moving Balance 1950
The second query is very long and complicated with a join .
What is the best way of joining these two queries
so that I have one column called invoice Type ( as the chart is based on this field)
that covers all the invoice types plus the moving balance
I assume that the place of the Moving Balance rows inside the result set is important.
You can do something like this:
select date, invoice_type, amount
from
(
select date, invoice_type, amount from query1
union all
select date, invoice_type, amount from query2
)
order by date, case invoice_type when 'Moving Balance' then 1 else 0 end
This first appends the results of the second query to the results of the first query and then reorders the resulting list first by date and then by the invoice type in such a way that the row with Moving balance will come last.
With the actual queries you have given, it should look something like this:
select date, invoice_type, amount
from
(
SELECT
CONVERT(VARCHAR(7),case_createddate, 111) AS Date,
case_invoicetype as invoice_type,
Sum(case_totalexvat) as amount
FROM cases AS ca
WHERE case_primaryCompanyid = 2174 and
datediff(m,case_createddate,getDate())
union all
select
CONVERT(VARCHAR(7),ca.case_createddate, 111) AS Date,
'Moving Balance' as Invoice_Type,
sum(mb.Amount) as Amount
from
cases as ca
left join (
select
case_primaryCompanyId as ID,
case_createdDate,
case_TotalExVat as Amount
from
cases
) mb
on ca. case_primaryCompanyId = mb.ID
and ca.case_createdDate >= mb.case_CreatedDate
where
ca.case_primaryCompanyId = 2174 and
ca.case_createdDate > DATEADD(m, -12, current_timestamp)
group by
case_primaryCompanyId,
CONVERT(VARCHAR(7),ca.case_createddate, 111)
order by ca.case_primaryCompanyid, CONVERT(VARCHAR(7),ca.case_createddate, 111)
)
order by date, case invoice_type when 'Moving Balance' then 1 else 0 end
You can use Union and can use Order by clause
Select * from (Query 1
Union
Query 2
) as a Order by a.Date Asc