Complicated TSQL custom ranking task - sql

I have tried a whole lot of variety of ranking solutions with joins and all to match the needs I want.
Sadly, I cannot come up with correct query to get the desired output.
I am really looking for any help to get explanation that would help me in future with these sort of tasks.
I have the following CTE table contains data values set:
type model price code
Shoes 1298 700,00 1
Shoes 1298 950,00 6
Shoes 1298 1050,00 4
Shoes 1321 970,00 2
Shoes 1750 1200,00 3
Shoes 1752 1150,00 5
Pants 1121 850,00 2
Pants 1121 850,00 4
Pants 1121 850,00 5
Pants 1232 350,00 8
Pants 1232 350,00 9
Pants 1232 400,00 7
Pants 1232 600,00 1
Pants 1233 600,00 3
Pants 1233 950,00 6
Pants 1233 970,00 12
Pants 1233 980,00 11
Pants 1260 350,00 10
Hats 1276 400,00 1
Hats 1288 400,00 6
Hats 1401 150,00 4
Hats 1408 270,00 5
Hats 1433 270,00 2
Hats 1434 290,00 3
Unified records numbering of CTE have to be done in the following manner: first there are the first models of the tables (Shoes, Pants and Hats), then the last models, after that - the second models in the tables, the penultimate, etc. In the case of exhaustion of the models of a particular type, number only remaining models of other types.
Here is the desired output:
Id type model price code
1 Shoes 1298 700.0000 1
2 Pants 1232 600.0000 1
3 Hats 1276 400.0000 1
4 Shoes 1298 950.0000 6
5 Pants 1233 970.0000 12
6 Hats 1288 400.0000 6
7 Shoes 1321 970.0000 2
8 Pants 1121 850.0000 2
9 Hats 1433 270.0000 2
10 Shoes 1752 1150.0000 5
11 Pants 1233 980.0000 11
12 Hats 1408 270.0000 5
13 Shoes 1750 1200.0000 3
14 Pants 1233 600.0000 3
15 Hats 1434 290.0000 3
16 Shoes 1298 1050.0000 4
17 Pants 1260 350.0000 10
18 Hats 1401 150.0000 4
19 Pants 1121 850.0000 4
20 Pants 1232 350.0000 9
21 Pants 1121 850.0000 5
22 Pants 1232 350.0000 8
23 Pants 1233 950.0000 6
24 Pants 1232 400.0000 7
I have updated the desired output (added code column) to better understand the sorting idea. It has to be done in interleaved manner with first coded numbers(i.e. lowest code) of types goes first then the last coded numbers(i.e. highest code) of types goes second, then first coded who's left goes first and then the last coded who's left and etc.

I can not really understand the ordering behind the scene, because the result set is not ordered nor by model nor by code, but here is the idea and you can play with orderings in CTEs:
WITH cte1 AS ( SELECT * ,
ROW_NUMBER() OVER (PARTITION BY type ORDER BY model, code) rn1
FROM #t),
cte2 AS ( SELECT * ,
ROW_NUMBER() OVER (PARTITION BY rn1 ORDER BY
CASE type WHEN 'Shoes' THEN 1
WHEN 'Pants' THEN 2
WHEN 'Hats' THEN 3 END) rn2
FROM cte1 )
SELECT * ,
ROW_NUMBER() OVER (ORDER BY rn1, rn2) rn
FROM cte2
Output:
type model price code rn1 rn2 rn
Shoes 1298 700.00 1 1 1 1
Pants 1121 850.00 2 1 2 2
Hats 1276 400.00 1 1 3 3
Shoes 1298 1050.00 4 2 1 4
Pants 1121 850.00 4 2 2 5
Hats 1288 400.00 6 2 3 6
Shoes 1298 950.00 6 3 1 7
Pants 1121 850.00 5 3 2 8
Hats 1401 150.00 4 3 3 9
Shoes 1321 970.00 2 4 1 10
Pants 1232 600.00 1 4 2 11
Hats 1408 270.00 5 4 3 12
Shoes 1750 1200.00 3 5 1 13
Pants 1232 400.00 7 5 2 14
Hats 1433 270.00 2 5 3 15
Shoes 1752 1150.00 5 6 1 16
Pants 1232 350.00 8 6 2 17
Hats 1434 290.00 3 6 3 18
Pants 1232 350.00 9 7 1 19
Pants 1233 600.00 3 8 1 20
Pants 1233 950.00 6 9 1 21
Pants 1233 980.00 11 10 1 22
Pants 1233 970.00 12 11 1 23
Pants 1260 350.00 10 12 1 24
Rn contains desired values.

You want the value interleaved. Here is how you can do this:
with cte as ( . . . )
select row_number() over (order by seqnum, charindex(type, 'ShoesPantsHats')) as id,
t.*
from (select cte.*,
row_number() over (partition by type order by (select NULL)) as seqnum
from cte
) t
order by seqnum,
charindex(type, 'ShoesPantsHats');
Note the order by clause for row_number(). SQL tables are inherently unordered, and if you care about the ordering of the results within each type, then put the appropriate logic there.

Try this,
with cte as (select type, model,price, ROW_NUMBER() over (partition by type order by type) as rowid from temp)
select * from cte order by rowid, type

Related

looking for values from another table where they do not exist in a given group

I have two tables:
SHOPPING
date
id_customer
id_shop
id_fruit
28.03.2018
7423
123
1
13.02.2019
8408
354
1
28.03.2019
7767
123
9
13.02.2020
8543
472
7
28.03.2020
8640
346
9
13.02.2021
7375
323
9
28.03.2021
7474
323
8
13.02.2022
7476
499
1
28.03.2022
7299
123
4
13.02.2023
8879
281
2
28.03.2023
8353
452
1
13.02.2024
8608
499
6
28.03.2024
8867
318
1
13.02.2025
7997
499
6
28.03.2025
7715
499
4
13.02.2026
7673
441
7
FRUITS
id_fruit
name
1
apple
2
pear
3
grape
4
banana
5
plum
6
melon
7
watermelon
8
orange
9
pineapple
I would like to find fruits that have never been bought in a specific id_shop
I tried with this:
SELECT
s.idshop,
s.id_fruit ,
f.name
FROM
shopping s
LEFT JOIN fruit f ON f.id_fruit = s.id_fruit
WHERE NOT EXISTS (
SELECT *
FROM
fruit f1
WHERE f1.id_fruit = s.id_fruit
)
but it does not work...
Yes, you need an OUTER JOIN, but that should be RIGHT JOIN along with NULL values picked from shopping table after join applied, considering your current query such as
SELECT f.*
FROM shopping s
RIGHT JOIN fruit f
ON f.id_fruit = s.id_fruit
WHERE s.id_fruit IS NULL
Demo

SQL Count for Each Category

I need to count rows based on 2 fields for grouping.
Animals (a)
id group_id strain_id death_date death_cause status
-----------------------------------------------------------------------
1 512 164 2015-12-01 Culled P
2 512 164 2015-12-02 Culled A
3 512 164 2015-12-02 Surplus B
4 512 230 2015-12-06 Culled A
5 512 164 2015-12-28 Culled A
6 512 230 2016-01-20 Culled B
7 512 230 2016-01-20 Surplus P
8 512 164 NULL NULL P
9 512 230 NULL NULL B
10 512 230 NULL NULL A
11 512 164 2016-01-25 Culled B
12 512 164 2016-02-29 Culled A
13 512 230 2016-02-03 Surplus P
14 512 230 2016-02-03 Culled A
Groups (g)
id group_name
--------------
512 Mice
Strain (s)
id strain_name
----------------
164 Strain 1
230 Strain 2
Group Animal Count (gac)
id total_animals alive_count dead_count
----------------------------------------------------------------------
512 14 3 11
Mating History (mh)
id animal_id history_type history_date
--------------------------------------------------------
1001 2 MA 2015-11-20
1002 2 MR 2015-12-01
1003 3 MA 2015-12-01
1004 6 FA 2015-12-21
1005 9 FA 2016-02-07
1006 10 MA 2016-01-27
1007 11 FA 2015-12-12
So when I group them by the strain_id and the death_cause this is what they should look like visually:
Strain 1 ---- Culled
1 512 164 2015-12-01 Culled P
2 512 164 2015-12-02 Culled A
5 512 164 2015-12-28 Culled A
11 512 164 2016-01-25 Culled B
12 512 164 2016-02-29 Culled A
Strain 1 ---- Surplus
3 512 164 2015-12-02 Surplus B
Strain 2 ---- Culled
4 512 230 2015-12-06 Culled A
6 512 230 2016-01-20 Culled B
14 512 230 2016-02-03 Culled A
Strain 2 ---- Surplus
7 512 230 2016-01-20 Surplus P
13 512 230 2016-02-03 Surplus P
What I want to get from the SQL query is the following result:
g_name s_name d_cause a_total c_alive c_dead c_pup c_breeder c_total
------------------------------------------------------------------------------
Mice Strain 1 Culled 12 3 9 1 2 5
Mice Strain 1 Surplus 12 3 9 0 1 1
Mice Strain 2 Culled 12 3 9 0 1 3
Mice Strain 2 Surplus 12 3 9 2 0 2
Basically I want to count the number of animals using 2 categories which in this case is the strain_name and the death_cause
Note that for an animal to be counted as a breeder (c_breeder), I have to look at the Mating History table and check if the animal_id has ever had any of these codes MA or FA.
I am using INNER JOIN on the groups, group_animal_count, and strains. I use LEFT JOIN for mating_history since animals with a status of P won't have records in that table since they're just pups and won't be involved with mating.
Try:
SELECT group_name, strain_name,death_cause, count(*) as Total
FROM ANIMALS a
JOIN GROUPS g ON a.group_id = g.id
JOIN STRAIN s ON a.strain_id = s.id
GROUP BY group_name, strain_name,death_cause
You can do the aggregation before joining the tables:
SELECT group_name, strain_name, death_cause, total
FROM (
SELECT group_id,
strain_id,
death_cause,
COUNT(*) AS total
FROM animals
GROUP BY group_id, strain_id, death_cause
) a
INNER JOIN groups g ON ( g.group_id = a.group_id )
INNER JOIN strain s ON ( s.strain_id = a.strain_id );

TSQL Interleaved sequence without join operations or user defined objects

Is that possible to create an interleaved sequence with first and last values going in turns untill data set is empty without using joins and user defined functions or procedures?
code type model price
1 Hats 1298 700,00
1 Shoes 1232 600,00
1 Pants 1276 400,00
2 Hats 1321 970,00
2 Shoes 1121 850,00
2 Pants 1433 270,00
3 Hats 1750 1200,00
3 Shoes 1233 600,00
3 Pants 1434 290,00
4 Hats 1298 1050,00
4 Shoes 1121 850,00
4 Pants 1401 150,00
5 Hats 1752 1150,00
5 Shoes 1121 850,00
5 Pants 1408 270,00
6 Hats 1298 950,00
6 Shoes 1233 950,00
6 Pants 1288 400,00
7 Shoes 1232 400,00
8 Shoes 1232 350,00
9 Shoes 1232 350,00
10 Shoes 1260 350,00
11 Shoes 1233 980,00
12 Shoes 1233 970,00
I have added extra spaces between rows to get the interleaved sequence idea.
You want to get the odd values going from lowest coded items (asc) and even values with the highest coded items (desc). You also want to order type by Hats,Shoes and Pants.
code type model price
1 Hats 1298 700,00
1 Shoes 1232 600,00
1 Pants 1276 400,00
6 Hats 1298 950,00
12 Shoes 1233 970,00
6 Pants 1288 400,00
2 Hats 1321 970,00
2 Shoes 1121 850,00
2 Pants 1433 270,00
5 Hats 1752 1150,00
11 Shoes 1233 980,00
5 Pants 1408 270,00
3 Hats 1750 1200,00
3 Shoes 1233 600,00
3 Pants 1434 290,00
4 Hats 1298 1050,00
10 Shoes 1260 350,00
4 Pants 1401 150,00
4 Shoes 1121 850,00
9 Shoes 1232 350,00
5 Shoes 1121 850,00
8 Shoes 1232 350,00
6 Shoes 1233 950,00
7 Shoes 1232 400,00
Right now I came up with solution that includes joins but I am looking for something that would work without using it.
My solution with using joins:
with cteasc as
(
select
ROW_NUMBER() over(order by code,charindex(type, 'HatsShoesPants'))id
,(ROW_NUMBER() over(partition by type order by code,charindex(type, 'HatsShoesPants')) + 1) / 2 offsetasc
,code,model,price,type
from mydata
),
ctedsc as
(
select
ROW_NUMBER() over (partition by type order by code desc)id
,code,model,price,type
from cteasc
)
select t1.id
,case
when t1.code%2=1
then LAG(t1.type,t1.code-t1.offsetasc,t1.type)over(partition by t1.type order by t1.id)
else LAG(t2.type,t1.code-t1.offsetasc,t1.type)over(partition by t1.type order by t1.id)
end type
,case
when t1.code%2=1
then LAG(t1.model,t1.code-t1.offsetasc,t1.model)over(partition by t1.type order by t1.id)
else LAG(t2.model,t1.code-t1.offsetasc,t1.model)over(partition by t1.type order by t1.id)
end model
,case
when t1.code%2=1
then LAG(t1.price,t1.code-t1.offsetasc,t1.price)over(partition by t1.type order by t1.id)
else LAG(t2.price,t1.code-t1.offsetasc,t1.price)over(partition by t1.type order by t1.id)
end price
from
cteasc t1
join ctedsc t2
on t1.code = t2.id
and t1.type = t2.type
The idea would be to generate two sequences:
The ascending items with row numbers 1, 3, 5, ...
The descending items with row numbers 2, 4, 6, ...
Then, we simply need to UNION ALL the sequences and sort them.
with ascItems as (
select *, row_number() over (order by someCol ASC) r * 2 - 1 as r from T
)
, descItems as (
select *, row_number() over (order by someCol DESC) r * 2 as r from T
)
select * from ascItems
union all
select * from descItems
order by r
This should require the table T to be available sorted twice (either by index or by 2xsorting). The UNION ALL should manifest itself as a cheap merge concat.

how to make Numbering rows in a query

I have a simple select statement:
Get numbering of rows from Product table in the following order: a name of the manufacturer in
--decreasing order of quantity of models produced by it (when there is identical quantity of models
--for a number of manufacturers, their names should follow in increasing alphabetic order), model (increasing order).
--Output: number in accordance with the above order, a name of the manufacturer (maker), model
RESULT SHOULD BE -
no maker model
1 A 1232
10 E 2112
11 E 2113
12 B 1121
13 B 1750
14 D 1288
15 D 1433
16 C 1321
2 A 1233
3 A 1276
4 A 1298
5 A 1401
6 A 1408
7 A 1752
8 E 1260
9 E 1434
MY QUERY :
with result as
(select *, count(*) over (partition by maker) as ModelsCount from Product)
select row_number() over (order by ModelsCount desc,model) AS No, Maker, Model
from result
MY RESULT:
no maker model
1 A 1232
10 E 2112
11 E 2113
12 B 1121
13 D 1288
14 D 1433
15 B 1750
16 C 1321
2 A 1233
3 A 1276
4 A 1298
5 A 1401
6 A 1408
7 A 1752
8 E 1260
9 E 1434

SQL query self join

I am working on a query for a report in Oracle 10g.
I need to generate a short list of each course along with the number of times they were offered in the past year (including ones that weren't actually offered).
I created one query
SELECT coursenumber, count(datestart) AS Offered
FROM class
WHERE datestart BETWEEN (sysdate-365) AND sysdate
GROUP BY coursenumber;
Which produces
COURSENUMBER OFFERED
---- ----------
ST03 2
PD01 1
AY03 2
TB01 4
This query is all correct. However ideally I want it to list those along with COURSENUMBER HY and CS in the left column as well with 0 or null as the OFFERED value. I have a feeling this involves a join of sorts, but so far what I have tried doesn't produce the classes with nothing offered.
The table normally looks like
REFERENCE_NO DATESTART TIME TIME EID ROOMID COURSENUMBER
------------ --------- ---- ---- ---------- ---------- ----
256 03-MAR-11 0930 1100 2 2 PD01
257 03-MAY-11 0930 1100 12 7 PD01
258 18-MAY-11 1230 0100 12 7 PD01
259 24-OCT-11 1930 2015 6 2 CS01
260 17-JUN-11 1130 1300 6 4 CS01
261 25-MAY-11 1900 2000 13 6 HY01
262 25-MAY-11 1900 2000 13 6 HY01
263 04-APR-11 0930 1100 13 5 ST03
264 13-SEP-11 1930 2100 6 4 ST03
265 05-NOV-11 1930 2100 6 5 ST03
266 04-FEB-11 1430 1600 6 5 ST03
267 02-JAN-11 0630 0700 13 1 TB01
268 01-FEB-11 0630 0700 13 1 TB01
269 01-MAR-11 0630 0700 13 1 TB01
270 01-APR-11 0630 0700 13 1 TB01
271 01-MAY-11 0630 0700 13 1 TB01
272 14-MAR-11 0830 0915 4 3 AY03
273 19-APR-11 0930 1015 4 3 AY03
274 17-JUN-11 0830 0915 14 3 AY03
275 14-AUG-09 0930 1015 14 3 AY03
276 03-MAY-09 0830 0915 14 3 AY03
SELECT
coursenumber,
COUNT(CASE WHEN datestart BETWEEN (sysdate-365) AND sysdate THEN 1 END) AS Offered
FROM class
GROUP BY coursenumber;
So, as you can see, this particular problem doesn't need a join.
I think something like this should work for you, by just doing it as a subquery.
SELECT distinct c.coursenumber,
(SELECT COUNT(*)
FROM class
WHERE class.coursenumber = c.coursenumber
AND datestart BETWEEN (sysdate-365) AND sysdate
) AS Offered
FROM class c
I like jschoen's answer better for this particular case (when you want one and only one row and column out of the subquery for each row of the main query), but just to demonstrate another way to do it:
select t1.coursenumber, nvl(t2.cnt,0)
from class t1 left outer join (
select coursenumber, count(*) cnt
from class
where datestart between (sysdate-365) AND sysdate
group by coursenumber
) t2 on t1.coursenumber = t2.coursenumber