I have the following setup (Java/Hibernate/PostgreSQL):
TeamName {
id: Long;
name: String;
team: Team;
....
}
Series {
id: Long;
season: Season;
dateScheduled: Date;
}
SeriesTeam {
id: Long;
series: Series;
team: TeamName;
}
SeriesTeam {
id: Long;
team: TeamName;
}
What I want to do is do a select of the past n series (say 10) or the next series from the current date. Here's what I have so far:
select s.* from series s
inner join series_teams st on st.series_id = s.id
inner join team_names tn on tn.id = st.team_name_id
where tn.id in (:teamIds) and s.date_scheduled < CURRENT_DATE
order by s.date_scheduled desc
But that is going to get me all the prior series for all teams and I will have to use Java to pick out what I want How would I go about doing what I want? Thanks!
EDIT: For example, say I wanted a limit of 10 per team name, and there are 24 teams, I would want max of 240 records returned to me. (assuming 10 exist before current date)
EDIT2: Here is the code that I want for a single team:
select s. from series s
inner join series_teams st on st.series_id = s.id
where st.team_name_id=85 and s.date_scheduled < CURRENT_DATE
order by s.date_scheduled desc
limit 10
I just need to be able to apply this for all the teams....I don't want to make x SQL calls for every team.
I think this will work. The syntax is in mysql and you can try it at that site the structure they have is similar to yours. you can adjust the limit value to change how many from each employee to return sorted by date. Probably add the current date check there too i guess.
Basically I joined all the needed tables together then created a new column that will tell me if that row is one we should return then added that check in the where clause.
https://www.w3schools.com/sql/trysql.asp?filename=trysql_op_in
SELECT e.employeeid, lastname,orderdate, orderdate in (select orderdate from
orders ord where e.employeeid=ord.employeeid order by orderdate limit 2) as
good FROM Employees as e join orders as o on e.employeeid=o.employeeid where
good=1 order by e.employeeid, o.orderdate;
for your case:
select s.id, s.season_id, s.date_scheduled, st.team_name_id,
s.date_scheduled in (
select s2.date_scheduled from series s2
inner join series_teams st2 on st2.series_id = s2.id
inner join team_names tn2 on tn2.id = st2.team_name_id
where tn.id =tn2.id and s2.date_scheduled < CURRENT_DATE
order by s.date_scheduled desc limit 5
) as foo
from series s
inner join series_teams st on st.series_id = s.id
inner join team_names tn on tn.id = st.team_name_id
where tn.id in (:teamIds) and foo = true
order by st.team_name_id, s.date_scheduled desc
Related
I have 4 tables:
CUSTOMER (idCustomer,name,phone,email, markdown)
ORDER (idOrder,day,Customer_idCustomer)
ORDERITEM (idOrderItem, quantity, Order_idOrder, Product_idProduct)
PRODUCT (idProduct,name,price,description).
What would be the query for:
Average customer spending (for each customer) in individual months
Number of ordered items?
I already wrote some queries on this database.
Since I do it for Java application with JDBC, maybe I can split these two queries and make calculation of average separately but what is the best approach? Is there a way how to do it just with two SQL queries?
Thank you for help.
Query I use for price reduction:
String sql = "SELECT c.markdown, SUM(oi.quantity * p.price)
FROM orderitem oi, `order` o, product p, customer c
WHERE c.idCustomer = o.Customer_idCustomer
AND oi.Order_idOrder=o.idOrder
AND oi.Product_idProduct=p.idProduct
AND o.idOrder=" + idOrderValue + "
GROUP BY oi.Order_idOrder";
Look here for database design (ERD)
You want an aggregation query with JOINs. Use proper JOIN syntax! Do not use commas in the FROM clause:
SELECT year(o.day) as yyyy,
month(o.day) as mm,
SUM(oi.quantity * p.price) / COUNT(DISTINCT c.idCustomer) as avg_customer_spending,
SUM(oi.quantity) as num_items
FROM customers c JOIN
orders o
ON c.idCustomer = o.Customer_idCustomer JOIN
orderitem oi
ON oi.Order_idOrder = o.idOrder JOIN
product p
ON oi.Product_idProduct = p.idProduct
GROUP BY year(o.day), month(o.day);
Okay so I've done some research and apparently, a left join can return more than 1 record based on the tables joined from the right.
my query is:
SELECT
ord.ID AS ord_id,
oli.sfid AS oli_sfid,
ord.HasMSISDN__c AS ord_HasMSISDN__c,
ord.dealer_code__c AS ord_dealer_code__c,
ord.recordtypeid AS ord_recordtypeid,
ord.order_number__c AS ord_order_number__c,
ord.status AS ord_status,
ord.opportunityid AS ord_opportunityid,
ord.sfid AS ord_sfid,
ord.cancelled_by__c AS ord_cancelled_by__c,
ord.cancelled_on__c AS ord_cancelled_on__c,
ord.created_by__c AS ord_created_by__c,
ord.created_on__c AS ord_created_on__c,
ord.docusign_email_address__c AS ord_docusign_email_address__c,
ord.esignature_resent_to__c AS ord_esignature_resent_to__c,
ord.esignature_resent_by__c AS ord_esignature_resent_by__c,
ord.esignature_resent_on__c AS ord_esignature_resent_on__c,
ord.pricebook2id AS ord_pricebook2id,
cont.opportunity__c AS cont_opportunity__c,
cont.sfid AS cont_sfid,
opp.isclosed AS opp_isclosed,
opp.sfid AS opp_sfid,
opp.recordtypeid AS opp_recordtypeid,
opp.pricebook2id AS opp_pricebook2id,
accban.sfid AS accban_sfid,
accban.ban__c AS accban_ban__c,
usr.sfid AS usr_sfid
FROM fullsbxsalesforce.order ord
LEFT JOIN fullsbxsalesforce.contract cont ON ord.contractid = cont.sfid
LEFT JOIN fullsbxsalesforce.opportunity opp ON cont.opportunity__c = opp.sfid
LEFT JOIN fullsbxsalesforce.user usr ON (ord.dealer_code__c = usr.dealer_code_bd__c OR ord.dealer_code__c = usr.Dealer_Code_Co_Sell__c OR ord.dealer_code__c = usr.Rep_Dealer_Code__c OR ord.dealer_code__c = usr.dealer_code_secondary__c) LEFT JOIN fullsbxsalesforce.account_ban_tax_id__c accban ON ord.ban_number__c = accban.ban__c
LEFT JOIN fullsbxsalesforce.orderitem oli ON ord.sfid = oli.orderid
WHERE ord.sfid = 'SPECIFIC ID'
Initially, I was under the impression that this would return 1 row. I am mistaken, it returns 3 rows because there are 3 different OLI's attached to the order. How can I ensure, or change my logic so that either, I am returned with a collection of OLI's in the same order or only return the first OLI so that I'm not dealing with 3 duplicates
If you want your rows returned in the same order, just add an ORDER BY <col_name_list> clause at the very end of your query.
Is an OLI a unique value? Which table defines an OLI?
If you always want this query to always return one single row, just add a LIMIT 1 to the end of your query.
If your query returns multiple OLI's and you only want one row per OLI, then you can use a window function:
SELECT ...
FROM (
-- Your initial query with new field added
SELECT ...
ROW_NUMBER() OVER(PARTITION BY OLI_field_name ORDER BY <ordering_clause>) AS RowRank
FROM ...
) src
WHERE RowRank = 1
This will return one row per <OLI_field_name>.
Update
If you want to just have one row per OLI and keep all the detailed info, use the window function method. Something like this:
SELECT *
FROM (
SELECT
ord.ID AS ord_id,
oli.sfid AS oli_sfid,
ord.HasMSISDN__c AS ord_HasMSISDN__c,
ord.dealer_code__c AS ord_dealer_code__c,
ord.recordtypeid AS ord_recordtypeid,
ord.order_number__c AS ord_order_number__c,
ord.status AS ord_status,
ord.opportunityid AS ord_opportunityid,
ord.sfid AS ord_sfid,
ord.cancelled_by__c AS ord_cancelled_by__c,
ord.cancelled_on__c AS ord_cancelled_on__c,
ord.created_by__c AS ord_created_by__c,
ord.created_on__c AS ord_created_on__c,
ord.docusign_email_address__c AS ord_docusign_email_address__c,
ord.esignature_resent_to__c AS ord_esignature_resent_to__c,
ord.esignature_resent_by__c AS ord_esignature_resent_by__c,
ord.esignature_resent_on__c AS ord_esignature_resent_on__c,
ord.pricebook2id AS ord_pricebook2id,
cont.opportunity__c AS cont_opportunity__c,
cont.sfid AS cont_sfid,
opp.isclosed AS opp_isclosed,
opp.sfid AS opp_sfid,
opp.recordtypeid AS opp_recordtypeid,
opp.pricebook2id AS opp_pricebook2id,
accban.sfid AS accban_sfid,
accban.ban__c AS accban_ban__c,
usr.sfid AS usr_sfid,
ROW_NUMBER() OVER(PARTITION BY oli.sfid ORDER BY <order_col>) AS RowRank -- Assigns a rank to each row with the same oli.sfid value
FROM fullsbxsalesforce.order ord
LEFT JOIN fullsbxsalesforce.contract cont ON ord.contractid = cont.sfid
LEFT JOIN fullsbxsalesforce.opportunity opp ON cont.opportunity__c = opp.sfid
LEFT JOIN fullsbxsalesforce.user usr ON (ord.dealer_code__c = usr.dealer_code_bd__c OR ord.dealer_code__c = usr.Dealer_Code_Co_Sell__c OR ord.dealer_code__c = usr.Rep_Dealer_Code__c OR ord.dealer_code__c = usr.dealer_code_secondary__c)
LEFT JOIN fullsbxsalesforce.account_ban_tax_id__c accban ON ord.ban_number__c = accban.ban__c
LEFT JOIN fullsbxsalesforce.orderitem oli ON ord.sfid = oli.orderid
WHERE ord.sfid = 'SPECIFIC ID'
) src
WHERE RowRank = 1 -- Only get one row per oli.sfid value
This assumes that oli.sfid is the OLI ID
Just change the outer SELECT * to return all the fields except RowRank. Also, modify the <order_col> value to determine which row you want to return for each oli.sfid.
Not very nice, but you can set a subquery as a table in your FROM clause instead of doing the left join with oli:
, (select * from fullsbxsalesforce.orderitem WHERE ord.sfid = orderid limit 1) oli
I would question whether or not you only want one record? I see two potential answers:
1. You want all the Order Items
That you are selecting from the OrderItems table implies you want records from there. If three records match your results, then it seems illogical that you would want to arbitrarily ignore some?
(I've seen this, and done it, but it is indicative of a problem)
2. You don't want Order Items at all
This seems more likely, based on your willingness to just discard the data.
That you are willing to just discard the data all together would imply that you don't actually want it in the first place. If you don't want it, just don't include the table at all.
Conclusion
Looking at your query I am guessing you are in case #2. The issue more likely that you have included a table unnecessarily.
Where you say oli.sfid AS oli_sfid, did you mean to get the sfid of the order? If so, you no longer need the join on OrderItems.
If after reading all of that, you are still sure you want just one, totally arbitrary, item from the order, order by and limit (as suggested by others) are the solution.
3. Edit: A third scenario
After reading a comment by the OP: If all that is being attempted to verify that OrderItems exist, aggregation may be another way to go:
SELECT ord.ID AS ord_id,
count(*) AS oli_count,
ord.HasMSISDN__c AS ord_HasMSISDN__c,
...
accban.ban__c AS accban_ban__c,
usr.sfid AS usr_sfid
FROM order ord
...
LEFT JOIN orderitem oli ON ord.sfid = oli.orderid
WHERE ord.sfid = 'SPECIFIC ID'
GROUP BY
ord.ID,
ord.HasMSISDN__c,
...
accban.ban__c,
usr.sfid
Though in this case, the group by would be difficult and painful to maintain.
If the objective is to ignore Orders that do not have items associated with them, then you don't want a left join, you want an inner join:
SELECT ord.ID AS ord_id,
...
oli.sfid AS oli_sfid,
usr.sfid AS usr_sfid
FROM fullsbxsalesforce.order ord
INNER JOIN orderitem oli ON ord.sfid = oli.orderid
LEFT JOIN contract cont ON ord.contractid = cont.sfid
LEFT JOIN opportunity opp ON cont.opportunity__c = opp.sfid
LEFT JOIN user usr ON ord.dealer_code__c = usr.dealer_code_bd__c
OR ord.dealer_code__c = usr.Dealer_Code_Co_Sell__c
OR ord.dealer_code__c = usr.Rep_Dealer_Code__c
OR ord.dealer_code__c = usr.dealer_code_secondary__c
LEFT JOIN account_ban_tax_id__c accban ON ord.ban_number__c = accban.ban__c
WHERE ord.sfid = 'SPECIFIC ID'
Note the change of position of orderitem in the table sequence.
Suppose you have a following JPA query:
select car.year, car.month, count(car) from Car car group by car.year, car.month
Before we query for results, we need to know how many records this query will return (for pagination, UI and so on). In other words we need something like this:
select count(*) from
(select car.year, car.month, count(car)
from Car car group by car.year)
But JPA/EclipseLink does not support subqueries in "from" clause. It there a way around it?
(Of course you can use plain SQL and native queries, but this is not an option for us)
A portable JPA solution:
select count(*) from Car c where c.id in
(select MIN(car.id) from Car car group by car.year, car.month)
You could also go with something like:
select COUNT(DISTINCT CONCAT(car.year, "#", car.month)) from car
but I expect this to be less performant due to operations with textual values.
What about:
select count(distinct car.year) from car
I have another approach to solve this issue . by using my approach you don't need to know the no of rows this query is going to return.
here it is your solution :-
you going to need two variables
1) pageNo (your page no should be 1 for first request to data base and proceeding request it should be incremental like 2 ,3 , 4 5 ).
2) pageSize.
int start = 0;
if(pageNo!=null && pageSize!=null){
start = (pageNo-1) * pageSize;
}else{
pageSize = StaticVariable.MAX_PAGE_SIZE; // default page size if page no and page size are missing
}
your query
em.createquery("your query ")
.setfirstResult(start)
.setMaxResult(pageSize)
.getResultList();
As #chris pointed out EclipseLink supports subqueries. But the subquery can't be the first one in the from-clause.
So I came up with the following workaround which is working:
select count(1) from Dual dual,
(select car.year, car.month, count(car)
from Car car group by car.year) data
count(1) is important as count(data) would not work
You have to add an entity Dual (If your database does not have a DUAL table, create one with just one record.)
This works but I still consider it a workaround that would only work if you allowed to create the DUAL table.
Simply you can use setFirstResult and setMaxResult to set record bound for query ,also use size of list to return count of records that query runs. like this :
Query query = em.createQuery("SELECT d.name, COUNT(t) FROM Department d JOIN
d.teachers t GROUP BY d.name");
//query.setFirstResult(5);
//query.setMaxResult(15); this will return 10 (from 5 to 15) record after query executed.
List<Object[]> results = query.getResultList();
for (int i = 0; i < results.size(); i++) {
Object[] arr = results.get(i);
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + " ");
}
System.out.println();
}
-----Updated Section------
JPA does not support sub-selects in the FROM clause but EclipseLink 2.4 current milestones builds does have this support.
See, http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#Sub-selects_in_FROM_clause
You can probably rewrite the query with just normal joins though.
Maybe,
Select a, size(a.bs) from A a
or
Select a, count(b) from A a join a.bs b group by a
I hope this helps you.
I'm fairly inexperienced when it comes to HQL, so I was wondering if anyone can help me out on this. I have a relationship where Student has a OneToMany relationship with StudentCollege. I'm trying to write a query that finds each student who has made an application, and how many applications they've submitted. I have the following query written, but I am unsure if it's even close to what I should be doing.
select distinct new ReportVO (stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name, sum(case when si.applied = 1 then 1 else 0 end) as numApplications) " +
"from StudentCollege as si join si.student as stu where stu.year <= 12 and stu.user.id = :userId and si.applied = 1 order by stu.last_name
When it runs the following exception is thrown:
org.hibernate.hql.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [package.location.ReportVO] [select distinct new ReportVO (stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name, sum(case when si.applied = 1 then 1 else 0 end) as numApplications) from package.location.StudentCollege as si join si.student as stu where stu.year <= 12 and stu.user.id = :userId and si.applied = 1 order by stu.last_name]
However I have a constructor for the VO object that takes in that same number of arguments, so I'm thinking the type returned from the sum is incorrect. I've also tried replacing numApplications with Integer and Count, but the same exception is thrown.
public ReportVO(int id, String firstName, String lastName, int year, String school, Integer numApplications)
Any help is appreciated!
I believe you are a missing a "group by" clause after the where clause.
select distinct new ReportVO (stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name, sum(case when si.applied = 1 then 1 else 0 end) as numApplications) " +
"from StudentCollege as si join si.student as stu where stu.year <= 12 and stu.user.id = :userId and si.applied = 1 group by stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name order by stu.last_name
I am working on a database project using both Java and SQL with an Oracle Database. I am new at working with databases and new at SQL. My question is: how would I be able to get the customer count and record each of their purchase history on a pivot table? For example, on the following table, I have Lee a total of 3 times, and purchased Item A, Item B, and Item C. Ann is there 3 times as well and purchased Item D, Item E, and Item F. I want to put their name, the number of occurrences and what they purchased on separate pivot table.
Row Customer Purchase_History
1 Lee Item A
2 Lee Item B
3 Lee Item C
4 Ann Item D
5 Ann Item E
6 Ann Item F
I've written some code attempting to do this, but when I compile and run, it will not give me the desired results. Here is my code:
String TableCount = "SELECT J.Row, J.Customer, J.Purchase_History, C.cnt" +
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
"FROM Table GROUP BY Customer") C ON J.Customer = C.Customer;
ResultSet rs = st.executeQuery(TableCount);
while(rs.next()){
st.executeUpdate("CREATE TABLE IF NOT EXISTS CUSTOMER_COUNT" +
"(TableCount , Purchase_History )");
String InsertIntoTable = String.format("INSERT INTO CUSTOMER_COUNT" +
"("TableCount","Purchase_History")" +
" VALUES ('%s','%s)");
}
What am I doing wrong here? Any help would be greatly appreciated!
Your query logic is fine. You should print out the query before running it. The problem would be these two lines (at least):
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
"FROM Table GROUP BY Customer") C ON J.Customer = C.Customer;
I assume you really mean:
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
"FROM Table GROUP BY Customer) C ON J.Customer = C.Customer";
In any case, when you print this out, you'll have the expression cntFROM table. You need a space:
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
" FROM Table GROUP BY Customer) C ON J.Customer = C.Customer";
But, an easier way to write your query is to use analytic functions:
select J.Row, J.Customer, J.Purchase_History,
count(*) over (partition by customer) as cnt
from table j