Shift rows based on parameter - reporting-services

I'm creating a new report for labels. To be able to use the whole paper with the labels i need to shift the rows down based on a parameter. Is this possible?
The first picture is how the label looks in the editor. Second picture is how the report looks when i run it. Last picture is how i want it.
Sql Query: SELECT
Parts.PartName
,Orders.OrderId
,OrderCustomFields.[PD Number]
,OrderedParts.PartId
FROM
Orders
INNER JOIN OrderCustomFields
ON Orders.OrderId = OrderCustomFields.Id
INNER JOIN OrderedParts
ON Orders.OrderId = OrderedParts.OrderId
INNER JOIN Parts
ON OrderedParts.PartId = Parts.Id
WHERE
OrderCustomFields.[PD Number] = #PDNumber
[Editor]
[]1
[Result]
[]2
[Desired report layout]
[]3
I tried to make it work with your awnser Alan. I do get a syntax error.
What am i doing wrong?
SELECT ROW_NUMBER() OVER (Order BY Parts.PartName) as rn, Parts.PartName, Orders.OrderId, OrderCustomFields.[PD Number], OrderedParts.PartId
FROM
Orders
INNER JOIN OrderCustomFields
ON Orders.OrderId = OrderCustomFields.Id
INNER JOIN OrderedParts
ON Orders.OrderId = OrderedParts.OrderId
INNER JOIN Parts
ON OrderedParts.PartId = Parts.Id
WHERE
OrderCustomFields.[PD Number] = #PDNumber
UNION All
SELECT * FROM
( -- get a sequence of numbers, make the rownumber negative
select
ROW_NUMBER() over(order by a.name) *-1 as rn
, NULL as Parts.PartName
, NULL as Orders.OrderId
, NULL as OrderCustomFields.[PD Number]
, NULL as OrderedParts.PartId
From sys.all_objects a
)topX
WHERE rn >= #skip *-1 -- filter to only row number that are positive (real records) or lower than the skip value
ORDER BY rn

As I don't have a sample of your data, this was done using the WideWorldImporters sample database but should be simple enough to follow.
I basically just appends x number of blanks rows at the start of the dataset based on a parameter #skip
declare #skip int = 3
SELECT
ROW_NUMBER() OVER(ORDER BY CustomerName, CityName) as rn
, CustomerName
, CityName
from Website.Customers c
WHERE BuyingGroupName ='Tailspin Toys'
UNION ALL
SELECT * FROM
( -- get a sequence of numbers, make the rownumber negative
select
ROW_NUMBER() over(order by a.name) *-1 as rn
, NULL as CustomerName
, NULL as CityName
from sys.all_objects a
) topX
WHERE rn >= #skip *-1 -- filter to only row number that are positive (real records) or lower than the skip value
ORDER BY rn
If you have a numbers table or similar you could use that instead of sys.all_objects, but this was just for illustration.

Related

MySQL, joining a table where you require the max value from the second table

I have the below query:
SELECT users_service.id, name
FROM users_service
LEFT JOIN
(SELECT * FROM activity)
activity ON (users_service.id = activity.user_service_id)
WHERE admin_id = 1
However, this returns as many results from the activity table as exist, ie multiple activity results for each admin_id entry.
I desire to return only the latest row from the activity table for each admin_id.
This could be entry_date or id.
I tried using distinct & max and limit 1, but these all produced strange behavior.
Use ROW_NUMBER():
SELECT us.id, a.name
FROM users_service us LEFT JOIN
(SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY a.user_service id ORDER BY ? DESC) as seqnum
FROM activity
) a
ON u.id = a.user_service_id AND seqnum = 1
WHERE u.admin_id = 1;
The ? is for the column that specifies the "most recent", which your question doesn't clarify.
You did not specify the column by which you determine the most recent activity. I call it datetime_col in the solution below:
SELECT users_service.id
, name
FROM users_service usv
LEFT
JOIN activity act
on act.users_service.id = usv.user_service_id
and act.datetime_col = (select max(datetime_col)
from activity act_
WHERE act_.user_service_id= act.user_service_id)

SQL: Select records based on comparison of two most recent associated records

Let's say we have a person table and survey table. survey is a set of attributes collected from a person at some point in time. Let's say survey has columns address and marriage_status
How do I select all persons whose address or marriage status has changed in the last survey?
Here's how I would write it if MySQL were able to magically interpret my intention:
SELECT *
FROM person
JOIN
(SELECT *
FROM survey
GROUP BY survey.person_id
ORDER BY survey.timestamp DESC
LIMIT 2 EACH) -- of course this part doesn't actually work. Trying to get last 2 records per person
surveys
ON surveys.person_id = person.id
WHERE surveys[0].address != surveys[1].address
OR surveys[0].marriage_status != surveys[1].marriage_status;
OR
SELECT *
FROM person
JOIN
(SELECT MOST RECENT survey FOR EACH person) latest_survey
ON latest_survey.person_id = person.id
JOIN
(SELECT SECOND MOST RECENT survey FOR EACH person) previous_survey
ON previous_survey.person_id = person.id
WHERE latest_survey.address != previous_survey.address
OR latest_survey.marriage_status != previous_survey.marriage_status;
This seems like a relatively straightforward query, but it's driving me crazy. I suspect I have tunnel vision and I'm not approaching this the right way.
EDIT: I am on MySQL v5. Based on the first couple answers, it seems like this might be the time to migrate to v8 (among other reasons)
So here's how I ended up doing it. It's a little long, but I think it's pretty straightforward? This felt amazing to get working.
(Note that underscores are used as prefixes in table aliases to help keep track of subquery depth)
SELECT person.*
FROM person
JOIN (
-- Join full survey data against each 'most recent' survey timestamp
SELECT s1.*
FROM survey s1
JOIN (
-- get most recent timestamp for each person
SELECT _s1.person_id, MAX(_s1.timestamp) timestamp
FROM survey _s1
GROUP BY person_id
) latest_surveys
ON latest_surveys.person_id = s1.person_id and latest_surveys.timestamp = s1.timestamp
) latest
ON latest.person_id = person.id
JOIN (
-- Join full survey data against each 'SECOND most recent' survey timestamp
select s2.*
from survey s2
JOIN (
-- to get SECOND most recent survey timestamp, do similar query, but exclude latest timestamp
SELECT _s2.person_id, MAX(_s2.timestamp) timestamp
FROM survey _s2
JOIN (
-- get most recent timestamp for each person (again)
SELECT __s2.person_id, MAX(__s2.timestamp) timestamp
FROM survey __s2
GROUP BY person_id
) _latest_surveys
-- Note the *NOT* equal here
ON _latest_surveys.person_id = _s2.person_id and _latest_surveys.timestamp != _s2.timestamp
GROUP BY _s2.person_id
) previous_surveys
ON previous_surveys.person_id = s2.person_id and previous_surveys.timestamp = s2.timestamp
) previous
ON previous.person_id = person.id
WHERE latest.address != previous.address
OR latest.marriage_status != previous.marriage_status;
Analytic functions make your question much more tractable. If you are not yet using MySQL 8+, then now would be a good time to upgrade. Assuming you are using MySQL 8+, we can try:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY s.timestamp DESC) rn
FROM person p
INNER JOIN survey s ON p.id = s.person_id
)
SELECT id
FROM cte
GROUP BY id
HAVING
MAX(CASE WHEN rn = 1 THEN address END) <> MAX(CASE WHEN rn = 2 THEN address END) OR
MAX(CASE WHEN rn = 1 THEN marriage_status END) <> MAX(CASE WHEN rn = 2 THEN marriage_status END);
The above query uses a pivot trick to isolate the latest, and second latest, addresses and marriage statuses for each person. It retains person id values for those whose latest and second latest addresses or marriage statuses are not identical.
This might be how you can achieve that:
SELECT *
FROM person
JOIN (
SELECT *,
MAX(survey_date) latest_survey,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(survey_date ORDER BY person_id, survey_date ASC),',',-2),',',1) previous_survey,
SUBSTRING_INDEX(GROUP_CONCAT(address ORDER BY person_id, survey_date ASC),',',-1) curadd,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(address ORDER BY person_id, survey_date ASC),',',-2),',',1) prevadd,
SUBSTRING_INDEX(GROUP_CONCAT(marriage_status ORDER BY person_id, survey_date ASC),',',-1) curms,
SUBSTRING_INDEX(SUBSTRING_INDEX(GROUP_CONCAT(marriage_status ORDER BY person_id, survey_date ASC),',',-2),',',1) prevms
FROM survey GROUP BY person_id
HAVING curadd != prevadd OR curms != prevms) A
ON person.id=A.person_id;
Using GROUP_CONCAT and SUBSTRING_INDEX to combine the data value then separate it again and using those to compare at the end. I know there are a bunch of ways to achieve without all these, like your second example is something that I think can be done but when I think about it, it's going to be a very long query. This query however, since you're not using MySQL 8+ is much shorter but the performance of this query is a concern especially on a large table.
It is not given, but I hope you have at least MySQL 8 or similar to have ability to use Common Table Expression. It can simplify the complex query.
The trick part is getting survey records #1 and #2 for each user. I will do it this way: see cte1 and cte2 definition
WITH
cte1 AS (
SELECT MAX(x1.id) AS id, x1.person_id
FROM survey x1
GROUP BY x1.person_id),
cte2 AS (
SELECT MAX(x2.id) AS id, x2.person_id
FROM survey x2
JOIN cte1 ON cte1.person_id = x2.person_id
AND cte1.id > x2.id
GROUP BY x2.person_id)
SELECT
p.*,
s1.address, s2.address address2,
s1.marriage_status, s2.marriage_status marriage_status2
FROM person AS p
JOIN (
cte1 JOIN survey s1 ON s1.id = cte1.id
) ON cte1.person_id = p.id
JOIN (
cte2 JOIN survey s2 ON s2.id = cte2.id
) ON cte2.person_id = p.id
WHERE
(s1.address <> s2.address)
OR (s1.marriage_status <> s2.marriage_status)
https://www.db-fiddle.com/f/hLwdHiZin4MkdUZ4aBz67H/2
Update: Thanks to Ian, I replaced MIN to MAX to get recent records

Double Aggregate Function Mysql

I want to take the maximum value from a series of returned values but I can't figure out a simple way to do it. My query returns all rows so 1/2 way there. I can filter it down with PHP but I'd like to do it all in SQL. I tried with a max subquery but that returned all results still.
DDL:
create table matrix(
count int(4),
date date,
product int(4)
);
create table products(
id int(4),
section int(4)
);
DML:
select max(magic_count), section, id
from (
select sum(count) as magic_count, p.section, p.id
from matrix as m
join products as p on m.product = p.id
group by m.product
) as faketable
group by id, section
Demo with my current try.
Only ids 1 and 3 should be returned from the sample data because they have the highest cumulative count for each of the sections.
Here's a second SQL fiddle that demonstrates the same issue.
Here you go:
select a.id,
a.section,
a.magic_count
from (
select p.id,
p.section,
magic_count
from (
select m.product, sum(count) as magic_count
from matrix m
group by m.product
) sm
join products p on sm.product = p.id
) a
left join (
select p.id,
p.section,
magic_count
from (
select m.product, sum(count) as magic_count
from matrix m
group by m.product
) sm
join products p on sm.product = p.id
) b on a.section = b.section and a.magic_count < b.magic_count
where b.id is null
see a simplified example (and other methods) in the manual entry for The Rows Holding the Group-wise Maximum of a Certain Column
see it working live here
Here you have solution without using JOINs, it has better performance than the other answer, which uses lot of JOINs:
select #rn := 1, #sectionLag := 0;
select id, section, count from (
select id,
case when #sectionLag = section then #rn := #rn + 1 else #rn := 1 end rn,
#sectionLag := section,
section,
count
from (
select id, section, sum(count) count
from matrix m
join products p on m.product = p.id
group by id, section
) a order by section, count desc
) a where rn = 1
Variables at the beginning are used to imitate window functions (LAG and ROW_NUMBER), which are available in MySQL 8.0 or higher (if you are using such version, let me know, so I will give you solution also with window functions).
DEMO
Another demo, where you can compare performance of my and the other query. It contains ~20K rows and my query tends to be almost 2 times faster.

Use aggregate values for farther calculation

I have the followin query:
SELECT contracts.id,
(SELECT sum(pos.sum_to_pay) FROM pos
where pos.contract_id=contracts.id and pos.is_draft=0) as paid,
(SELECT sum(acts.amount) FROM acts
where acts.contract_id=contracts.id) as acts_sum
from contracts
it works but i want to add another result field to_pay that should be calculated like acts_sum - paid = to_pay.
I'm trying to do it like this:
SELECT contracts.id,
(SELECT sum(pos.sum_to_pay) FROM pos
where pos.contract_id=contracts.id and pos.is_draft=0) as paid,
(SELECT sum(acts.amount) FROM acts
where acts.contract_id=contracts.id) as acts_sum,
(acts_sum - paid) as to_pay
from contracts
but I got the error Unknown column 'acts_sum'. How can i find to_pay value based on acts_sum and paid?
Do it with a subquery like this
SELECT acts_sum, paid, (acts_sum - paid) as to_pay FROM
(SELECT contracts.id,
(SELECT sum(pos.sum_to_pay) FROM pos
where pos.contract_id=contracts.id and pos.is_draft=0) as paid,
(SELECT sum(acts.amount) FROM acts
where acts.contract_id=contracts.id) as acts_sum,
from contracts ) subq
You could rewrite your query using joins, correlated sub queries sometimes considered as a costly solution
select c.id,
COALESCE(a.acts_sum,0),
COALESCE(p.paid,0),
COALESCE(a.acts_sum,0) - COALESCE(p.paid,0) as to_pay
from contracts c
left join (
SELECT contract_id,sum(sum_to_pay) paid
FROM pos
where is_draft=0
group by contract_id
) p on c.id = p.contract_id
left join (
SELECT contract_id,sum(amount) acts_sum
FROM acts
group by contract_id
) a on c.id = a.contract_id

How to have sales of every product for each day in SSIS?

My data is pretty standard, but it's in multiple flat files, hence why I'm using SSIS. I have:
Date
Product ID
Number sold
Seller ID
I have 30 products, so for every day I'd like to have 30 line, one for each product. That way, I could see that on date X, we've sold 10 product A, 0 product B, and so forth.
I'm not sure which tool I should be using. I've tried with Aggregate, but I don't think it's the right way to do it. How should I proceed?
You can generate the rows using cross join and then use left join to bring in the data.
Assuming your table has all the dates and all the products (although not necessarily all the combinations). The basic idea is:
select d.date, p.product, coalesce(t.numsold, 0)
from (select distinct date from t) d cross join
(select distinct product from t) p left join
t
on t.date = d.date and t.product = p.product;
Oh, I noticed that your data has a supplier id, so there are probably multiple rows for a given product on a given date. Hence, some aggregation is needed. Here is one way:
select d.date, p.product, coalesce(sum(t.numsold), 0)
from (select distinct date from t) d cross join
(select distinct product from t) p left join
t
on t.date = d.date and t.product = p.product
group by d.date, p.product;
If you can't guarantee that all dates will be in the product table, you can generate a calendar table in a cte (or preferably persist it to a table).
declare #FromDate date;
declare #ThruDate date;
set #FromDate = '2010-01-01';
set #ThruDate = '2020-12-31';
with x as (
select top (cast(sqrt(datediff(day, #FromDate, #ThruDate)) as int) + 1)
[number]
from [master]..spt_values v
)
/* Date Range CTE */
,calendar as (
select top (1+datediff(day, #FromDate, #ThruDate))
DateValue = convert(date,dateadd(day,
row_number() over (order by x.number)-1,#FromDate)
)
from x cross join x as y
order by DateValue
)
--select DateValue from calendar into dbo.calendar;
From there you can adapt #GordonLinoff's answer to use the calendar table or cte and determine the date range in the subquery's where clause.
select [date] = cal.datevalue, p.product, NumSold= coalesce(sum(t.numsold), 0)
from (
select datevalue
from calendar
where datevalue >= '2016-12-01'
and datevalue <= '2016-12-31'
) as cal
cross join (
select distinct product
from t
) as p
left join t on t.date = cal.datevalue and t.product = p.product
group by cal.datevalue, p.product;
More about calendar tables and number generation:
Aaron Bertrand - Generate a set or sequence without loops
generate-a-set-1
generate-a-set-2
generate-a-set-3
David Stein - Creating a Date Table/Dimension on SQL 2008
Michael Valentine Jones - F_TABLE_DATE