Is there a way to rewrite this statement without sub queries? - sql

I am trying to combine 4 queries for a monthly report so that I don't have to run them seperately. Our internal accounting software appears doesn't support sub queries so this statement doesn't work.
select left(salesgroupcode,4) as "Sales Group",
count(Number_of_products),
count(Number_of_discontinued),
count(Number_not_uploaded),
count(Number_sitting)
from (select
case when quantityavailable > 1 then 1 end Number_of_products,
case when quantityavailable > 1 and discontinued = true then 1 end Number_of_discontinued,
case when quantityavailable > 1 and z_datefirstuploaded is null then 1 end Number_not_uploaded,
case when quantityavailable > 1 and z_datefirstuploaded is null and dateoflastsale <= '01/01/2019' then 1 end Number_sitting
from icprod
) icprod
I don't have any other info about the specific rules etc that the software allows so I'm happy to try anything.
Thanks in advance.
Any help is appreciated

Yes, it is possible:
select left(salesgroupcode,4) as "Sales Group",
count(*),
count(case when discontinued = true then 1 end ),
count(case when z_datefirstuploaded is null then 1 end),
count(case when z_datefirstuploaded is null and dateoflastsale <= '01/01/2019')
from icprod
where quantityavailable > 1
group by left(salesgroupcode,4)

Just use conditional aggregation:
select sum(case when quantityavailable > 1 then 1 else 0 end ) as Number_of_products,
sum(case when quantityavailable > 1 and discontinued = true then 1 else 0 end) as Number_of_discontinued,
sum(case when quantityavailable > 1 and z_datefirstuploaded is null then 1 else 0 end) as Number_not_uploaded,
sum(case when quantityavailable > 1 and z_datefirstuploaded is null and dateoflastsale <= '2019-01-01' then 1 else 0 end) as Number_sitting
from icprod;
Notes that I changed the date constant to be ISO 8601 standard format. In some databases, you may need to precede that with date.
This can in turn be simplified to:
select count(*) as Number_of_products,
sum(case when discontinued = true then 1 else 0 end) as Number_of_discontinued,
sum(case when z_datefirstuploaded is null then 1 else 0 end) as Number_not_uploaded,
sum(case when z_datefirstuploaded is null and dateoflastsale <= '2019-01-01' then 1 else 0 end) as Number_sitting
from icprod
where quantityavailable > 1

Related

Get users that have at least one product code and also more than 10 of the other product code

The table that needs to be queried looks like this:
ID
UserID
ProductCodes
1
33
9999
2
456
3051
3
456
9999
4
456
3051
4
33
9999
How would I write a SQL query to find out which users have at least one productCodes = '9999' and also have more than 10 productCodes <> '9999'?
You can use GROUP BY and HAVING:
SELECT
UserID
FROM dbo.YourTable
GROUP BY
UserId
HAVING SUM(CASE WHEN ProductCodes = '9999' THEN 1 ELSE 0 END) >= 1
AND COUNT(DISTINCT ProductCodes) >= 11
;
Use Case or Intersect (case is more performant)
SELECT UserID, SUM (case when ProductCodes='9999' then 1 else 0 end) PC9999
, SUM (case when ProductCodes<>'9999' then 1 else 0 end) PCNot9999
FROM dbo.Users
WHERE ProductCodes='9999'
GROUP BY UserID
HAVING SUM (case when ProductCodes='9999' then 1 else 0 end)>0
AND SUM (case when ProductCodes<>'9999' then 1 else 0 end) >10
I ended up going with this. It allows us to get specific with how many times a '9999' product code has been used in comparison with other codes.
SELECT
UserID
FROM Session_Hst
GROUP BY
UserID
HAVING SUM(CASE WHEN ProductCodes = '9999' THEN 1 ELSE 0 END) >= 1
AND COUNT(CASE WHEN ProductCodes <> '9999' THEN 1 ELSE null END ) >= 10
;

How to use having condition in SQL query

SELECT
userid,
CASE
WHEN (COUNT(CASE
WHEN onlinesportsgamewagers != 0
THEN 1
ELSE null
END)
+ COUNT(CASE
WHEN depositmade_amt != 0
THEN 1
ELSE null
END)) >= 10
THEN "VIP"
ELSE "NON-VIP"
END as VIPcheck
FROM
player_activity
WHERE
userid = 2023410
GROUP BY
year(txndate), month(txndate)
This query determines the user's VIP status for each month.
Ultimately, I want to have a query that determines if the user achieved VIP status for at least 3 months (including the current month). For the time being, it's only user 2023410, but eventually I want to run this for the whole database.
Therefore my ultimate output would be:
User - VIPcheck (3 different months w/ active status)
(one row per userID)
HAVING COUNT(CASE WHEN (COUNT(CASE WHEN onlinesportsgamewagers != 0
THEN 1
ELSE null
END)
+ COUNT(CASE WHEN depositmade_amt != 0
THEN 1
ELSE null
END)) >= 10
THEN 1
ELSE 0
END)
Tried the above having statement, but it didn't work. Any suggestions?
If I understand correctly, this gets the VIP status for one user by month:
SELECT userid, year(txndate), month(txndate),
(CASE WHEN SUM(CASE WHEN onlinesportsgamewagers <> 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN depositmade_amt <> 0 THEN 1 ELSE 0 END) >= 10
THEN 'VIP'
ELSE 'NON-VIP'
END) as VIPcheck
FROM player_activity
GROUP BY userid, year(txndate), month(txndate);
Another aggregation will get what you want:
SELECT userid,
(CASE WHEN SUM(VIPcheck = 'VIP') >= 3 THEN 'SUPER-VIP'
WHEN SUM(VIPcheck = 'VIP') >= 1 THEN 'VIP'
ELSE 'HOI POLLOI'
END) as status
FROM (SELECT userid, year(txndate), month(txndate),
(CASE WHEN SUM(CASE WHEN onlinesportsgamewagers <> 0 THEN 1 ELSE 0 END) +
SUM(CASE WHEN depositmade_amt <> 0 THEN 1 ELSE 0 END) >= 10
THEN 'VIP'
ELSE 'NON-VIP'
END) as VIPcheck
FROM player_activity
GROUP BY userid, year(txndate), month(txndate)
) uym
GROUP BY userid;

sql subquery that collects from 3 rows

I have a huge database with over 4 million rows that look like that:
Customer ID Shop
1 Asda
1 Sainsbury
1 Tesco
2 TEsco
2 Tesco
I need to count customers that within last 4 weeks had shopped in all 3 shops Tesco Sainsbury and Asda. Can you please advice if its possible to do it with subqueries?
This is an example of a "set-within-sets" subquery. You can solve it with aggregation:
select customer_id
from Yourtable t
where <shopping date within last four weeks>
group by customer_id
having sum(case when shop = 'Asda' then 1 else 0 end) > 0 and
sum(case when shop = 'Sainsbury' then 1 else 0 end) > 0 and
sum(case when shop = 'Tesco' then 1 else 0 end) > 0;
This structure is quite flexible. So if you wanted Asda and Tesco but not Sainsbury, then you would do:
select customer_id
from Yourtable t
where <shopping date within last four weeks>
group by customer_id
having sum(case when shop = 'Asda' then 1 else 0 end) > 0 and
sum(case when shop = 'Sainsbury' then 1 else 0 end) = 0 and
sum(case when shop = 'Tesco' then 1 else 0 end) > 0;
EDIT:
If you want a count, then use this as a subquery and count the results:
select count(*)
from (select customer_id
from Yourtable t
where <shopping date within last four weeks>
group by customer_id
having sum(case when shop = 'Asda' then 1 else 0 end) > 0 and
sum(case when shop = 'Sainsbury' then 1 else 0 end) > 0 and
sum(case when shop = 'Tesco' then 1 else 0 end) > 0
) t

Proper way to create a pivot table with crosstab

How do I convert the following query into a pivot table using crosstab?
select (SUM(CASE WHEN added_customer=false
THEN 1
ELSE 0
END)) AS CUSTOMERS_NOT_ADDED, (SUM(CASE WHEN added_customer=true
THEN 1
ELSE 0
END)) AS CUSTOMERS_ADDED,
(select (SUM(CASE WHEN added_sales_order=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS SALES_ORDER_NOT_ADDED,
(select (SUM(CASE WHEN added_sales_order=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS SALES_ORDER_ADDED,
(select (SUM(CASE WHEN added_fulfillment=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS ITEM_FULFILLMENT_NOT_ADDED,
(select (SUM(CASE WHEN added_fulfillment=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS ITEM_FULFILLMENT_ADDED,
(select (SUM(CASE WHEN added_invoice=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS INVOICE_NOT_ADDED,
(select (SUM(CASE WHEN added_invoice=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS INVOICE_ADDED,
(select (SUM(CASE WHEN added_ra=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS RA_NOT_ADDED,
(select (SUM(CASE WHEN added_ra=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS RA_ADDED,
(select (SUM(CASE WHEN added_credit_memo=false
THEN 1
ELSE 0
END))
FROM shipments_data
) AS CREDIT_MEMO_NOT_ADDED,
(select (SUM(CASE WHEN added_credit_memo=true
THEN 1
ELSE 0
END))
FROM shipments_data
) AS CREDIT_MEMO_ADDED
FROM shipments_data;
This query gives me data in a standard row format however I would like to show this as a pivot table in the following format:
Added Not_Added
Customers 100 0
Sales Orders 50 50
Item Fulfillemnts 0 100
Invoices 0 100
...
I am using Heroku PostgreSQL, which is running v9.1.6
Also, I'm not sure if my above query can be optimized or if this is poor form. If it can be optimized/improved I would love to learn how.
The tablefunc module that supplies crosstab() is available for 9.1 (like for any other version this side of the millennium). Doesn't Heroku let you install additional modules? Have you tried:
CREATE EXTENSION tablefunc;
For examples how to use it, refer to the manual or this related question:
PostgreSQL Crosstab Query
OR try this search - there are a couple of good answers with examples on SO.
To get you started (like most of the way ..) use this largely simplified and re-organized query as base for the crosstab() call:
SELECT 'added'::text AS col
,SUM(CASE WHEN added_customer THEN 1 ELSE 0 END) AS customers
,SUM(CASE WHEN added_sales_order THEN 1 ELSE 0 END) AS sales_order
,SUM(CASE WHEN added_fulfillment THEN 1 ELSE 0 END) AS item_fulfillment
,SUM(CASE WHEN added_invoice THEN 1 ELSE 0 END) AS invoice
,SUM(CASE WHEN added_ra THEN 1 ELSE 0 END) AS ra
,SUM(CASE WHEN added_credit_memo THEN 1 ELSE 0 END) AS credit_memo
FROM shipments_data
UNION ALL
SELECT 'not_added' AS col
,SUM(CASE WHEN NOT added_customer THEN 1 ELSE 0 END) AS customers
,SUM(CASE WHEN NOT added_sales_order THEN 1 ELSE 0 END) AS sales_order
,SUM(CASE WHEN NOT added_fulfillment THEN 1 ELSE 0 END) AS item_fulfillment
,SUM(CASE WHEN NOT added_invoice THEN 1 ELSE 0 END) AS invoice
,SUM(CASE WHEN NOT added_ra THEN 1 ELSE 0 END) AS ra
,SUM(CASE WHEN NOT added_credit_memo THEN 1 ELSE 0 END) AS credit_memo
FROM shipments_data;
If your columns are defined NOT NULL, you can further simplify the CASE expressions.
If performance is crucial, you can get all aggregates in a single scan in a CTE and split values into two rows in the next step.
WITH x AS (
SELECT count(NULLIF(added_customer, FALSE)) AS customers
,sum(added_sales_order::int) AS sales_order
...
,count(NULLIF(added_customer, TRUE)) AS not_customers
,sum((NOT added_sales_order)::int) AS not_sales_order
...
FROM shipments_data
)
SELECT 'added'::text AS col, customers, sales_order, ... FROM x
UNION ALL
SELECT 'not_added', not_customers, not_sales_order, ... FROM x;
I also demonstrate two alternative ways to build your aggregates - both built on the assumption that all columns are boolean NOT NULL. Both alternatives are syntactically shorter, but not faster. In previous testes all three methods performed about the same.

GROUP BY WEEK with SQL

I have the following:
http://sqlfiddle.com/#!6/226ae/1
I'm trying to now add one row for each week of the year, and filter the contacts accordingly. CONTACTS has a datetime column. The new table will look like:
Status 1 Status 2 Status 3
Week 1 3 4 2
Week 2 1 5 3
Week 3 2 2 4
I think that DATEADD needs to be used, however I'm at a loss in terms of how to begin changing my query.
I do know that MySQL has a GROUP BY WEEK command, but I don't think that SQL has an equivalent. What's the best way to accomplish this?
You can use DATEPART(), this groups by both the week and the year in the event you have data spanning multiple years:
SELECT
'Week ' + cast(datepart(wk, created) as varchar(2)) Week,
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
group by datepart(wk, created), year(created)
See SQL Fiddle with Demo
Adding the year to the final result:
SELECT
'Week ' + cast(datepart(wk, created) as varchar(2)) Week,
year(created) year,
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
group by datepart(wk, created), year(created)
See SQL Fiddle with demo
You can use the datepart function to extract the week from a date.
The query becomes:
SELECT datepart(week, created) as week,
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
group by datepart(week, created)
SqlFiddle: http://sqlfiddle.com/#!6/226ae/6tsq
You might try using the group by clause in your query:
SELECT
DATE_FORMAT( created, '%u' ) week_number,
SUM(case WHEN status = 1 then 1 else 0 end) Status1,
SUM(case WHEN status = 2 then 1 else 0 end) Status2,
SUM(case WHEN status = 3 then 1 else 0 end) Status3,
SUM(case WHEN status = 4 then 1 else 0 end) Status4,
SUM(case WHEN status = 5 then 1 else 0 end) Status5
FROM contacts
GROUP BY DATE_FORMAT( created, '%u' )
I'm assuming you are talking about mysql.
The DATE_FORMAT function is documented here:
http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-format