Order by followed by dependent order by - sql

I have a table with part of data like below . I have done order by on edition_id .
Now there is further requirement of ordering laungauge_id which depends on value of edition_id.
Edition_id refers to city from which a newspaper is published.
Language_id refers to different languages in which newspaper is
published.
So suppose edition_id = 5 it means New Delhi.
For New Delhi language_id are 13(English ), 5 (Hindi) ,1(Telugu ),4(Urdu).
What i want is to display for New Delhi , is display all English articles first , followed by hindi , followed by Telugu followed by Urdu.
If edition_id=1 then order of language_id should be 13,1,2.
Similarly ,
If edition_id=5 then order of language_id should be 13,5,1,4
Right now what I have is
Edition_id | Language_id
1 1
1 2
1 13
1 1
1 13
1 2
5 4
5 1
5 1
5 4
5 13
5 5
5 13
What is required
Edition_id | Language_id
1 13
1 13
1 1
1 1
1 2
1 2
5 13
5 13
5 5
5 1
5 1
5 4
5 4
How to do this ? Please help.
Is something like this possibe
Select * from <table>
order by edition_id ,
case when edition=6 then <order specified for language_id ie 13,5,1,4>

I would create a supplementary ranking table. I would then JOIN to provide your sort order. Eg:
EDITION_SORT_ORDER
EDITION_ID LANGUAGE_ID RANK
---------- ----------- ----
1 13 1
1 1 2
1 2 3
5 13 1
5 5 2
5 1 3
5 4 4
Using this table in a query might look like this:
SELECT E.EDITION_ID, E.LANGUAGE_ID
FROM <TABLE> E LEFT OUTER JOIN EDITION_SORT_ORDER S ON
E.EDITION_ID = S.EDITION_ID AND
E.LANGUAGE_ID = S.LANGUAGE_ID
ORDER BY S.RANK
This way you can add other rules in future, and it isn't a huge mess of CASE logic.
Alternatively, if you want to avoid a JOIN, you could create a stored function which did a similar lookup and returned a rank (based on passed parameters of EDITION_ID and LANGUAGE_ID).
If you must use CASE, then I'd confine it to a function so you can re-use the logic elsewhere.

If there is no mathematical logic behind it, I would insert another column that can be used for proper sorting.
If you cannot do this, you can simply type out the rules for the relation like this:
Order By Edition_Id,
case Edition_id
when 1 then
case Language_id
when 13 then 1
when 1 then 2
when 2 then 3
end
when 5 then
case Language_id
when 13 then 1
when 5 then 2
when 1 then 3
when 4 then 4
end
end

without a fixed order colum you could things like that, but the logic is not comprehensible.
Assuming first criteria is length of Language_id,
Second is Edition_id= Language_id,
rest is order of Language_id it could or work this way:
Declare #t table(Edition_id int, Language_id int)
insert into #t values
(1, 1),
(1, 2),
(1, 13),
(1, 1),
(1, 13),
(1, 2),
(5, 4),
(5, 1),
(5, 1),
(5, 4),
(5, 13),
(5, 5),
(5, 13);
Select * from #t
order by Edition_id,Case when len (Cast(Language_ID as Varchar(10)))=1 then '1' else '0' end
+case when Edition_id=Language_id then '0' else '1' end
,Language_ID

You've probably considered this but if your desired ordering is always based of the actual alphabetical name of the language then there would usually be a table with the language description that you could join with and then sort by. I base this on your quote below.
...English articles first , followed by hindi , followed by Telugu
followed by Urdu.
SELECT E.EDITION_ID, E.LANGUAGE_ID, LN.LANGUAGE_NAME
FROM <TABLE> E LEFT OUTER JOIN <LANGUAGE_NAMES> LN ON
E.LANGUAGE_ID = LN.LANGUAGE_ID
ORDER BY 1, 3

Related

case end / self-join postres sql

I am trying to process data within the same table.
Input:
Table
id sort value
1 1 1
2 1 8
3 2 0
4 1 2
What I want to achieve is obtain for each id, the first encountered value for all value equal to its sort, and this ordered by id.
Output
Table
id sort value new
1 1 1 1
2 1 8 1
3 2 0 0
4 1 2 1
I tried to self join the table, but I constantly get relation not found. I tried with a case statement but I don't see how can I connect to the same table, I get the same error, relation not found.
The beauty of SQL is that many requirements (yours included) can be verbosely described in very similar way they are finally coded:
with t(id, sort, value ) as (values
(1, 1, 1),
(2, 1, 8),
(3, 2, 0),
(4, 1, 2)
)
select t.*
, first_value(value) over (partition by sort order by id) as "new"
from t
order by id
id
sort
value
new
1
1
1
1
2
1
8
1
3
2
0
0
4
1
2
1
fiddle

Using recursive CTE to generate hierarchy results ordered by depth without the use of heiarchyid

I would like to query hierarchy results ordered by depth first without the use of SQL's heiarchyid built in function. Essentially, I am hoping to accomplish the depth ordering without any fancy functions.
I have provided a temp table below that contains these records:
Id
p_Id
order1
name1
1
null
1
josh
2
null
2
mary
3
null
3
george
4
1
1
joe
5
1
2
jeff
6
2
1
marg
7
2
2
moore
8
2
3
max
9
3
1
gal
10
3
2
guy
11
4
1
tod
12
4
2
ava
13
9
1
ron
14
9
2
bill
15
9
100
pat
where p_Id is the id of the parent record, and order1 is essentially just the ordering of which the depth first output should be displayed. To show why my query does not fully work, I made the order1 of the last record 100 instead of say, 3. However this should not ultimately matter since 100 and 3 both come after the previous order1 value, 2.
An example of a correct result table is shown below:
Id
p_Id
order1
name1
Descendants
1
null
1
josh
josh
4
1
1
joe
josh/joe
11
4
1
tod
josh/joe/tod
12
4
2
ava
josh/joe/ava
5
1
2
jeff
josh/jeff
2
null
2
mary
mary
6
2
1
marg
mary/marg
7
2
2
moore
mary/moore
8
2
3
max
mary/max
3
null
3
george
george
9
3
1
gal
george/gal
13
9
1
ron
george/gal/ron
15
9
2
bill
george/gal/bill
14
9
100
pat
george/gal/pat
10
3
2
guy
george/guy
Where an example of my results are shown below:
Id
p_Id
order1
name1
Descendants
levels
1
null
1
josh
josh
.1
4
1
1
joe
josh/joe
.1.1
11
4
1
tod
josh/joe/tod
.1.1.1
12
4
2
ava
josh/joe/ava
.1.1.2
5
1
2
jeff
josh/jeff
.1.2
2
null
2
mary
mary
.2
6
2
1
marg
mary/marg
.2.1
7
2
2
moore
mary/moore
.2.2
8
2
3
max
mary/max
.2.3
3
null
3
george
george
.3
9
3
1
gal
george/gal
.3.1
13
9
1
ron
george/gal/ron
.3.1.1
15
9
100
pat
george/gal/pat
.3.1.100
14
9
2
bill
george/gal/bill
.3.1.2
10
3
2
guy
george/guy
.3.2
where I have created a levels column that essentially concatenates the order1 values and separates them with a period. This almost returns the correct results, but due to the fact that I am ordering by this string (of numbers and periods), the levels value of .3.1.100 will come before .3.1.2 , which is not what the desired output should look like. I am sure there is a different method to return the correct depth order. See below for the code that generates a temp table, and the code that I used to generate the incorrect output that I have so far.
if object_id('tempdb..#t1') is not null drop table #t1
CREATE TABLE #t1 (Id int, p_Id int, order1 int, name1 varchar(150))
INSERT into #t1 VALUES
(1, null, 1, 'josh'),
(2, null, 2, 'mary'),
(3, null, 3, 'george'),
(4, 1, 1, 'joe'),
(5, 1, 2, 'jeff'),
(6, 2, 1, 'marg'),
(7, 2, 2, 'moore'),
(8, 2, 3, 'max'),
(9, 3, 1, 'gal'),
(10, 3, 2, 'guy'),
(11, 4, 1, 'tod'),
(12, 4, 2, 'ava'),
(13, 9, 1, 'ron'),
(14, 9, 2, 'bill'),
(100, 9, 100, 'pat');
select * from #t1
-- Looking to generate heiarchy results ordered by depth --
; with structure as (
-- Non-recursive term.
-- Select the records where p_Id is null
select p.Id,
p.p_Id,
p.order1,
p.name1,
cast(p.name1 as varchar(64)) as Descendants,
cast(concat('.', p.order1) as varchar(150)) as levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term.
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)) as Descendants,
cast(concat(p.levels, '.', c.order1) as varchar(150)) as levels
from #t1 c -- c being the 'child' records
inner join structure p -- p being the 'parent' records
on c.p_Id = p.Id
)
select *
from structure
order by replace(levels, '.', '') asc
Take II. As pointed out by OP my original answer fails for more than 10 children. So what we can do (OP's suggestion) is pad the values out with zeros to a constant length. But what length? We need to take the largest number of children under a node and add this to the largest value or order, so for the example provided this is 100 + 3, and then take the length of that (3) and pad every order with zeros to 3 digits long. This means we will always be ordering as desired.
declare #PadLength int = 0;
select #PadLength = max(children)
from (
select len(convert(varchar(12),max(order1)+count(*))) children
from #t1
group by p_Id
) x;
-- Looking to generate heiarchy results ordered by depth --
with structure as (
-- Non-recursive term
-- Select the records where p_Id is null
select
p.Id [Id]
, p.p_Id [ParentId]
, p.order1 [OrderBy]
, p.name1 [Name]
, cast(p.name1 as varchar(64)) Descendants
, concat('.', right(replicate('0',#Padlength) + convert(varchar(12),p.order1), #PadLength)) Levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select
c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)),
concat(p.levels, '.', right(replicate('0',#Padlength) + convert(varchar(12),c.order1), #PadLength))
from #t1 c -- c being the 'child' records
inner join structure p on c.p_Id = p.Id -- p being the 'parent' records
)
select *
from structure
order by replace(levels, '.', '') asc;
Note: This answer fails in the case when there are more than 10 children under a particular node. Leaving for interest.
So this issue you have run into is that you are ordering by a string not a number. So the string 100 comes before the string 2. But you need to order by a string to take care of the hierarchy, so one solution is to replace order1 with row_number() based on the order1 column while its still a number and use the row_number() to build your ordering string.
So you replace:
cast(concat(p.levels, '.', c.order1) as varchar(150)) as levels
with
cast(concat(p.levels, '.', row_number() over (order by c.Order1)) as varchar(150))
giving a full query of
with structure as (
-- Non-recursive term.
-- Select the records where p_Id is null
select p.Id,
p.p_Id,
p.order1,
p.name1,
cast(p.name1 as varchar(64)) as Descendants,
cast(concat('.', p.order1) as varchar(150)) as levels
from #t1 p
where p.p_Id is null
union all
-- Recursive term.
-- Treat the records from previous iteration as parents.
-- Stop when none of the current records have any further sub records.
select c.Id,
c.p_Id,
c.order1,
c.name1,
cast(concat(p.Descendants, '/', c.name1) as varchar(64)) as Descendants,
cast(concat(p.levels, '.', row_number() over (order by c.Order1)) as varchar(150))
from #t1 c -- c being the 'child' records
inner join structure p -- p being the 'parent' records
on c.p_Id = p.Id
)
select *
from structure
order by replace(levels, '.', '') asc;
Which returns the desired results.
Note: good question, well written.

PostgreSQL Order by stepping numbers

I need to order records from a table by a column. The old system the customer was using manually selected level 1 items, then all the children of level 1 items for level 2, then so on and so forth through level 5. That is horrible IMHO, as it requires hundreds of queries and calls to the DB.
So in the new DB structure I'm trying to make it all one query to the DB if possible and have it order it correctly the first time. The customer wants it displayed to them this way so I have no choice but to figure out a way to order this way.
This is an example of the items and their level codes (1 being the single digit codes, 2 the 2 digit codes, 3 for 4 digit codes, 4 for 6 digit codes and level 5 for 8 digit codes):
It's supposed to order basically everything that starts with a 5 goes under Code 5. Everything that starts with a 51 goes under code 51. If you look at the column n_mad_id it links to the "Mother" ID of the code that is the mother of that code, so code 51's mother is code 5. Code 5101's mother is code 51. Code 5201's mother is code 52. And so on and so forth.
Then the n_nivel column is the level that the code belongs to. Each code has a level and a mother. The top level codes (i.e. 1, 2, 3, 4, 5) are all level 1 since they are only one digit.
I was hoping that there might be an easy ORDER BY way to do this. I've been playing with it for two days and can't seem to get it to obey.
The absolutely simplest way would be to cast the n_cod field to text and then order on that:
SELECT *
FROM mytable
WHERE left(n_cod::text, 1) = '5' -- optional
ORDER BY n_cod::text;
Not pretty, but functional.
You could consider changing your table definition to make n_cod of type char(8) because you do not use it as a number anyway (in the sense of performing calculations). That would make the query a lot faster.
Interesting task. As I understand that you want to get result in order like
n_id n_cod n_nivel n_mad_id
10 5 1 0
11 51 2 10
12 5101 3 11
14 510101 4 12
...
13 52 2 10
...
?
If yes then it may do the trick:
with recursive
tt(n_id, n_mad_id, n_cod, x) as (
select t.n_id, t.n_mad_id, t.n_cod, array[t.n_id]
from yourtable t where t.n_mad_id = 0
union all
select t.n_id, t.n_mad_id, t.n_cod, x || t.n_id
from tt join yourtable t on t.n_mad_id = tt.n_id)
select * from tt order by x;
Here is my original test query:
create table t(id, parent) as values
(1, null),
(3, 1),
(7, 3),
(5, 3),
(6, 5),
(2, null),
(8, 2),
(4, 2);
with recursive
tt(id, parent, x) as (
select t.id, t.parent, array[t.id] from t where t.parent is null
union all
select t.id, t.parent, x || t.id from tt join t on t.parent = tt.id)
select * from tt order by x;
and its result:
id | parent | x
----+--------+-----------
1 | (null) | {1}
3 | 1 | {1,3}
5 | 3 | {1,3,5}
6 | 5 | {1,3,5,6}
7 | 3 | {1,3,7}
2 | (null) | {2}
4 | 2 | {2,4}
8 | 2 | {2,8}
(8 rows)
Read about recursive queries.

Sorting in SQL w/ multiple conditions

I'm new to SQL, how would I go along with sorting the following:
I have three different type of dev projects(type 1,2, and 3). Type 1 and 2 also have an analysis that is linked with them. The analysis and dev type 1 and 2 will have a linking parent id, how would I sort with these specs:
Process Items in the following sequence
Dev type 3 projects
Dev type 1 or 2 projects combined with their analysis item (dev item columns take precedence analysis item columns)
Remaining analysis projects with no related dev projects
*EDIT* the lower ParentID should come first after these three specs
Example columns of the table are:
|ParentID| AnalysisItemID | DevItemId | DevType |
1 Null 2 1
4 5 Null Null
6 8 Null Null
8 Null 9 3
6 Null 7 2
1 3 Null Null
Sorted Output would be:
|ParentID| AnalysisItemID | DevItemId | DevType |
8 Null 9 3
1 Null 2 1
1 3 Null Null
6 Null 7 2
6 8 Null Null
4 5 Null Null
To me, it looks like you want to sort the data by the parent id, with the ordering based on the maximum devtype for the parent.
For this, you need to join to a summary table to get the information for the order by:
select t.*
from t join
(select parentId, max(devtype) as maxdt
from t
group by parentId
) p
on t.parentId = p.parentId
order by (case when maxdt = 3 then 1
when maxdt in (1, 2) then 2
else 3
end), parentId, devtype desc
It's ugly, but you can order by the result of a CASE in SQL. I imagine something similar to:
SELECT * FROM MyTable
ORDER BY CASE WHEN DevType = 3 THEN 1
WHEN DevType IN (1, 2) THEN AnalysisItemID + 1
ELSE [bignumber] + AnalysisItemID END
could do what you require.
Obviously, that case can also be encapsulated into a user-defined function for readability if required. There's a chance that that could affect performance, however.

SQL-Query - finding pattern of another table

I have a table with colors:
COLORS
idColor Name
------- ------
4 Yellow
5 Green
6 Red
And I have another table with data:
PRODUCTS
idProduct idCategory idColor
--------- ---------- -------
1 1 4
2 1 5
3 1 6
4 2 10
5 2 11
6 2 12
7 3 4
8 3 5
9 3 8
10 4 4
11 4 5
12 4 6
13 5 4
14 6 4
15 6 5
I just want return rows from Products when the idColor values from table Colors (4, 5, 6) are present in the second table and IdCategory has exactly 3 elements with the same idColor values 4, 5, 6.
For this example, The query should return:
IdCategory
----------
1
4
Try this:
SELECT idCategory
FROM PRODUCTS
GROUP BY idCategory
HAVING COUNT(*) = 3
AND COUNT(DISTINCT CASE WHEN idColor IN (4,5,6) THEN idColor END) = 3
Here is a demo for you to try.
UPDATED
If you want to dynamically filter the results depending on the values of the table `COLOR
SELECT idCategory
FROM PRODUCTS P
LEFT JOIN (SELECT idColor, COUNT(*) OVER() TotalColors
FROM COLORS) C
ON P.idColor = C.idColor
GROUP BY idCategory
HAVING COUNT(*) = MIN(C.TotalColors)
AND COUNT(DISTINCT C.idColor) = MIN(C.TotalColors)
Here is a fiddle with this example.
You can use aggregates to make sure it has all 3 colors, and also to make sure it DOESN'T have any other colors. Something like this:
SELECT *
FROM
(
SELECT idCategory
, SUM(CASE WHEN idColor IN (4, 5, 6) THEN 1 ELSE 0 END) AS GoodColors
, SUM(CASE WHEN idColor NOT IN (4, 5, 6) THEN 1 ELSE 0 END) AS BadColors
FROM Products
GROUP BY idCategory
) t0
WHERE GoodColors = 3 AND BadColors = 0
Note, if the 4, 5, 6 is found more than once per idCategory then a different technique must be employed. But from your example, it doesn't appear that way.
I am guessing that you would like to perform this task based on data in a table, rather than hardcoding the values 4, 5, and 6 (like in some of the answers given). To that end, in my solution I created a dbo.ColorSets table that you can fill with as many different sets of colors as you want, then run the query and see all the product Categories that match those color Sets. The reason I didn't just use your dbo.Color table is that it appeared to be the lookup table, complete with color names, so it didn't seem like the right one to be picking out a particular set of colors rather than the entire list possible.
I used a technique that will maintain good performance even on huge amounts of data, as compared to other query methods that use aggregates exclusively. No matter what method one uses, this task will pretty much always require a scan of the entire Products table because you can't compare all the rows without, well, comparing all the rows. But the JOIN is on indexable columns and is only for the candidates that have a very good chance of being proper matches, so the amount of work required is greatly reduced.
Here's what the ColorSets table looks like:
CREATE TABLE dbo.ColorSets (
idSet int NOT NULL,
idColor int NOT NULL,
CONSTRAINT PK_ColorSet PRIMARY KEY CLUSTERED (idSet, idColor)
);
INSERT dbo.ColorSets
VALUES
(1, 4),
(1, 5),
(1, 6), -- your color set: yellow, green, and red
(2, 4),
(2, 5),
(2, 8) -- an additional color set: yellow, green, and purple
;
And the query (see this working in a SqlFiddle):
WITH Sets AS (
SELECT
idSet,
Grp = Checksum_Agg(idColor)
FROM
dbo.ColorSets
GROUP BY
idSet
), Categories AS (
SELECT
idCategory,
Grp = Checksum_Agg(idColor)
FROM
dbo.Products
GROUP BY
idCategory
)
SELECT
S.idSet,
C.idCategory
FROM
Sets S
INNER JOIN Categories C
ON S.Grp = C.Grp
WHERE
NOT EXISTS (
SELECT *
FROM
(
SELECT *
FROM dbo.ColorSets CS
WHERE CS.idSet = S.idSet
) CS
FULL JOIN (
SELECT *
FROM dbo.Products P
WHERE P.idCategory = C.idCategory
) P
ON CS.idColor = P.idColor
WHERE
CS.idColor IS NULL
OR P.idColor IS NULL
)
;
Result:
idSet idCategory
1 1
2 3
1 4
If I understand your question, this should do it
select distinct idCategory
from Products
where idColors in (4,5,6)