Context:
A table of transactions coming from an external system is queried to create elements on a local system with two main columns:
A child that is not yet persisted in the local database
Possible multiple parents semi-colon separated (which can be persisted locally or not yet created)
I'm having a problem with sorting a table that looks like this:
Child Parents
A B;C
B C;E
C F;X
What I want to accomplish is to make sure the Parent always gets created before the child:
Child Parents
C F;X
B C;E
A B;C
F, X & E in this example represents elements already persisted in the local database that could be on my query
I spent some time thinking this through and it seems it's related to trees / graphs on which I have little to no knowledge...
Any idea what kind of sorting we are dealing with here and possible java or SQL implementations?
Edit:
Every Child Element is an element that is not persisted yet on the database
A child can either have a list of parents already persisted (like F, X & E) or parents I still need to persist (in which case they need to precede said child) or a mix of both (like B with C;E parents E already persisted and C comes in this transaction and must precede B)
Can not alter the database, but can reorder things freely in java
Thanks in advance
Partial Solution
I've adapted Joshua's solution and it seems to work this way:
(basically use like statement because we can have something like AA;BB;CC as a parent)
SELECT DISTINCT
s1.child,
s1.parents,
CASE
WHEN s2.parents IS NULL THEN 0
WHEN s2.parents LIKE '%' + s1.child + '%' THEN 1
ELSE 3
END
AS x
FROM
sample s1
LEFT JOIN sample s2 ON s2.parents LIKE '%' + s1.child + '%'
WHERE
s1.treateddate IS NULL
ORDER BY
CASE
WHEN s2.parents IS NULL THEN 0
WHEN s2.parents LIKE '%' + s1.child + '%' THEN 1
ELSE 3
END
DESC
This solution doesn't account for a child which is a parent at the same time!
Solution Java
I found the solution in Java to reordering the dataset, this is just pseudo code because I'm using a proprietary library...
private void sortByParentFirst(rows) {
for (int i = 0; i < rows.size(); i++) {
for (int j = 0; j < rows.size(); j++) {
boolean match = false;
//parents comma seperated at j contain child i
if (Parents[j].contains(Child[i]) ) {
match = true;
}
//this means we found an occurrence of the child i at a previous position j
if (match && j < i) {
//swap rows i & j
swap(i,j);
break;
}
}
}
}
Here the table sample contains your data
select * from sample
chid parents
---- ----------
A B;C
B C;E
C F;X
select * from sample
drop table #sample
drop table #sample2
select * into #sample from sample
select * into #sample2 from sample where 1=2
alter table #sample2 add counter int
declare #count int
select #count = count(1) from #sample
declare #child char(1)
declare #counter int = 1
while(#count > 0)
begin
select #child = t1.chid
from #sample t1
inner join #sample t2 on CHARINDEX(t1.chid,t2.parents) = 0
group by t1.chid
having count( t1.chid) = 1
insert into #sample2
select *,#counter from #sample where chid = #child
delete from #sample where chid = #child
select #count = count(1) from #sample
set #counter = #counter + 1
end
select chid, parents from #sample2 order by counter
select distinct s1.* ,
case when s2.parents is null and s3.parents is null then 0
when s1.child = left(s2.parents,1) and s3.parents is null then 1
when s1.child = right(s3.parents,1) and s2.parents is null then 2
else 3 end as x
from sample s1
left join sample s2 on s1.child = left(s2.parents,1)
left join sample s3 on s1.child = right(s3.parents,1)
order by
case when s2.parents is null and s3.parents is null then 0
when s1.child = left(s2.parents,1) and s3.parents is null then 1
when s1.child = right(s3.parents,1) and s2.parents is null then 2
else 3 end desc
SQL Fiddle Example
Join the table back to itself twice looking for a match to a parent. If the real date is more than just a character, then you can use CHARINDEX to locate the delimiter and per form a left or right based off the location of the returned starting position.
Related
Here is the JPQL query for postgresql database where 'priceSortOrder' and 'currentMarketPrice' are parameters. currentMarketPrice is an integer value. priceSortOrder can be 'ASC' or 'DESC'.
select product
from tms.product
order by (case (:priceSortOrder)
when NULL then 1
when 'ASC' then case
when product.exchange_Rate_Type = 'FIXED_RATE' then product.fixed_Rate
else (:currentMarketPrice) end
when 'DESC' then case
when product.exchange_Rate_Type = 'FIXED_RATE' then product.fixed_Rate
else (:currentMarketPrice) end end) DESC
I can run the query well. But it doesn't serve my purpose. As, I want to sort it based on priceSortOrder. But, adding ASC or DESC inside when like below gives the syntax error (ELSE, END or WHEN expected, got 'ASC'). I can add hardcode 'ASC' or 'DESC' at the very end only and thus always sorted in descending order.
Error code: (But explains my purpose)
select product
from tms.product
order by (case (:priceSortOrder)
when NULL then 1
when 'ASC' then case
when product.exchange_Rate_Type = 'FIXED_RATE' then product.fixed_Rate
else (:currentMarketPrice) ASC end
when 'DESC' then case
when product.exchange_Rate_Type = 'FIXED_RATE' then product.fixed_Rate
else (:currentMarketPrice) end DESC end)
What can I do to get my expected output?
As an example to have different directions in the ORDER BY clause of your SQL Statement, you can use the following code :
DECLARE #ORDER_DIRECTION BIT = 1;
SELECT * FROM S_PRS.T_PERSONNE_PHYSIQUE_PSP
ORDER BY COALESCE(CAST(NULLIF(#ORDER_DIRECTION, 1) AS VARCHAR(1)), PSP_NOM) ASC,
COALESCE(CAST(NULLIF(#ORDER_DIRECTION, 0) AS VARCHAR(1)), PSP_NOM) DESC;
Another ways is :
SELECT * FROM S_PRS.T_PERSONNE_PHYSIQUE_PSP
ORDER BY iif(#ORDER_DIRECTION = 1, PSP_NOM, NULL) DESC,
iif(#ORDER_DIRECTION = 0, NULL, PSP_NOM) ASC;
When #ORDER_DIRECTION = 1 the order is ASC, when 0 DESC for the PSP_NOM (person's name) column.
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.
I'm trying to implement some code for get friends list.
First:
- I have my String id. E.G: 784717
- I have an string with 100 numbers. E.G: 7781,5913,551949194,4919491,...,444131 (One string separated by ,)
- I have 3000 records in my Database with different numbers. (With numbers I mean some kind of ID)
- Of my 100 numbers only 8 are registered in my database.
Question:
How can I know what numbers are registered in the database and insert in other table the relationship?
My table Relationship have this columns:
*number1 - (Here should be my ID)
*number2 - (1 of the 100 numbers that exists)
So in my table Relationship should be 8 new rows.
I tried with :
EXEC('SELECT * FROM Accounts WHERE ID IN(' +#in_mystring+')')
but i don't know how insert in the other table or if is efficiently
Assuming this is SQL Server, and with the help of a parser function
For example
Select * from [dbo].[udf-Str-Parse]('7781,5913,551949194,4919491,...,444131',',')
Returns
Key_PS Key_Value
1 7781
2 5913
3 551949194
4 4919491
5 ...
6 444131
From this sub-query, you can join the results to your Accounts Table
Perhaps something like this
Select A.*
From Accounts A
Join (Select * from [dbo].[udf-Str-Parse]('7781,5913,551949194,4919491,...,444131',',')) B
on A.Key_Value =A.ID
The UDF -- If 2016, There is a native parser.
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
-- Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1) NOT NULL , Key_Value varchar(max))
As
Begin
Declare #intPos int,#SubStr varchar(max)
Set #IntPos = CharIndex(#delimeter, #String)
Set #String = Replace(#String,#delimeter+#delimeter,#delimeter)
While #IntPos > 0
Begin
Set #SubStr = Substring(#String, 0, #IntPos)
Insert into #ReturnTable (Key_Value) values (#SubStr)
Set #String = Replace(#String, #SubStr + #delimeter, '')
Set #IntPos = CharIndex(#delimeter, #String)
End
Insert into #ReturnTable (Key_Value) values (#String)
Return
End
I have two tables in SQL Server :
Table A
ID Num
11 tj55
4 tj40
Table B
ID NUM A_ID
3 se400 4
5 se500 4
I have a stored procedure which will return for each row in A all corresponding rows in B, I have this result
NumA NumB
tj40 se400
tj40 se500
Code:
ALTER PROCEDURE [dbo].[Proc]
#param nvarchar(MAX),
AS
BEGIN
SELECT
aa.Num,
bb.Num
FROM
[dbo].[A] as aa
INNER JOIN
[dbo].[B] AS bb ON bb.A_ID = aa.ID
WHERE
aa.Num = #param
ORDER BY
aa.Num
END
I want to use a call my StoredProcedure in my Java application with JPA to have the two rows generated (my goal is to have List containing all the NumB (in this case 'se400' and 'se500')
StoredProcedureQuery query2 = em.createStoredProcedureQuery("Proc");
query2.registerStoredProcedureParameter("param", String.class,ParameterMode.IN);;
query2.setParameter("param", "tj40");
query2.execute();
List<Object> res = query2.getResultList();
for (int i=0; i<res.size(); i++){
System.out.println(res.get(i).toString());
}
it founds the two rows but it returns
[Ljava.lang.Object;#94814
[Ljava.lang.Object;#00856
Could you help me please?
Thank you very much
In my application I am using the following way
List i1 = query2.getResultList();
while(i1.hasNext()){
Object[] object =(Object[]) i1.next();
//your rest code
//object[0] will contain numA and object[1] will contain numB
}
I have a table that consists of a unique id, and a few other attributes. It holds "schedules". Then I have another table that holds a list of all the times each schedule has or will "fire". This isn't the exact schema, but it's close:
create table schedule (
id varchar(40) primary key,
attr1 int,
attr2 varchar(20)
);
create table schedule_times (
id varchar(40) foreign key schedule(id),
fire_date date
);
I want to query the schedule table, getting the attributes and the next and previous fire_dates, in Java, sometimes ordering on one of the attributes, but sometimes ordering on either previous fire_date or the next fire_date. Ordering by the attributes is easy, I just stick an "order by" into the string while I'm building my prepared statement. I'm not even sure how to go about selecting the last fire_date and the next one in a single query - I know that I can find the next fire_date for a given id by doing a
SELECT min(fire_date)
FROM schedule_times
WHERE id = ? AND
fire_date > sysdate;
and the similar thing for previous fire_date using max() and fire_date < sysdate. I'm just drawing a blank on how to incorporate that into a single select from the schedule so I can get both next and previous fire_date in one shot, and also how to order by either of those attributes.
You can do that using two sub-queries in Left Joins.
This has the advantage of returning NULL for your fire_dates if there is no next/previous schedule.
Select id, attr1, attr2, next_fire_date, previous_fire_date
From schedule s
Left Join ( Select id, Min(fire_date) As next_fire_date
From schedule_times st
Where st.fire_date > Sysdate
Group By id ) n
On ( n.id = s.id )
Left Join ( Select id, Max(fire_date) As previous_fire_date
From schedule_times st
Where st.fire_date < Sysdate
Group By id ) p
On ( p.id = s.id )
You can then add your ORDER BY on next_fire_date or previous_fire_date.
If performance matters, create a compound index on schedule_times( id, fire_date ), this will allow the sub-queries to read only from this index.
Try something like this:
select schedule.*,
(
select max(si.fire_date) from schedule_times si where si.id = schedule.id and si.fire_date < sysdate
) as prevfire,
(
select min(si.fire_date) from schedule_times si where si.id = schedule.id and si.fire_date > sysdate
) as nextfire
from schedule
where id = ?
order by attr1
Modify the query to
SELECT Distinct S.ID, ATTR1, ATTR2,
LEAD(FIRE_DATE, 1, SYSDATE) OVER (PARTITION BY S.ID ORDER BY S.ID) NEXT_FIRE_DATE,
LAG(FIRE_DATE, 1, SYADATE) OVER (PARTITION BY S.ID ORDER BY S.ID) PREV_FIRE_DATE
FROM SCHEDULE S,SCHEDULE_TIMES ST WHERE ST.ID=S.ID;
THIS WAS SIMPLE QUERY. YOU CAN TRY THIS.
LEAD has the ability to compute an expression on the next rows (rows which are going to come after the current row) and return the value to the current row. The general syntax of LEAD is shown below:
LEAD (sql_expr, offset, default) OVER (analytic_clause)
sql_expr is the expression to compute from the leading row.
offset is the index of the leading row relative to the current
row. offset is a positive integer
with default 1.
default is the value to return if the offset points to a row
outside the partition range.
The syntax of LAG is similar except that the offset for LAG goes into the previous rows.