SQL query using fn_Split to find multiple values in column - sql

This is a followup to my earlier question which got me part of the way to the goal.
Here is what I'm starting with:
SELECT * FROM MyTable WHERE County IN (SELECT value From dbo.fn_Split(#counties, ','))
Here's the scenario: In my table I've got a column named County. Each record can have multiple counties in the County column delimited with commas (I know this is bad form, I didn't do it). For example: county1, county22, county41. Some records may have only one county (say county13) others might have all the counties. So: county1, count2, county3... through county45 (yes, it's terrible, I know).
In the app I'm trying to build users can select multiple counties or even all counties in the same format as above (county1, county2, county3...). Thanks to Martin's help in the previous question I can get it to return records that have each of the counties individually but not the records that might contain all of the counties.
For example: The user selects county4, county26. I need to have the records returned that have just county4 and county26 as well as any that might contain both of them as part of a larger set (like all 45 of them).
Hope this is clear and I didn't make it more convoluted than necessary. Any assistance is very, very, very much appreciated!
To illustrate:
County
Record1 county1, county14, county26
Record2 county14
Record3 county1, county2, ... through county45
User Submission: county1, county26
Returns: Record1 and Record3

Not sure if I understood the question, but here is how I interpret it:
You need to return rows from your table for one or more selected items.
Also, you want to be able to select ALL items at once without passing the whole list.
I'd do that using Stored Procedure with 2 parameters:
#Selection SMALLINT
#TVP_County CountyTableType (It is Table Valued Variable. See: https://msdn.microsoft.com/en-us/library/bb510489.aspx)
If #Selection = 1 then you join #TVP_County with your table to get results.
If #Selection = 0 you return ALL records from your table w/o join and do not use #TVP_County at all.
If #Selection = -1 then you exclude #TVP_County items from your table. In that case you will be able to do a reverse marks. User will be able to select just few counties, which he/she does not want to see.
Of cause, within a stored procedure you have to implement the logic to run three different queries depending on the first parameter.

Related

SQL 2 JOINS USING SINGLE REFERENCE TABLE

I'm trying to achieve 2 joins. If I run the 1st join alone it pulls 4 lots of results, which is correct. However when I add the 2nd join which queries the same reference table using the results from the select statement it pulls in additional results. Please see attached. The squared section should not be being returned
So I removed the 2nd join to try and explain better. See pic2. I'm trying to get another column which looks up InvolvedInternalID against the initial reference table IRIS.Practice.idvClient.
Your database is simply doing as you tell it. When you add in the second join (confusingly aliased as tb1 in a 3 table query) the database is finding matching rows that obey the predicate/truth statement in the ON part of the join
If you don't want those rows in there then one of two things must be the case:
1) The truth you specified in the ON clause is faulty; for example saying SELECT * FROM person INNER JOIN shoes ON person.age = shoes.size is faulty - two people with age 13 and two shoes with size 13 will produce 4 results, and shoe size has nothing to do with age anyway
2) There were rows in the table joined in that didn't apply to the results you were looking for, but you forgot to filter them out by putting some WHERE (or additional restriction in the ON) clause. Example, a table holds all historical data as well as current, and the current record is the one with a NULL in the DeletedOn column. If you forget to say WHERE deletedon IS NULL then your data will multiply as all the past rows that don't apply to your query are brought in
Don't alias tables with tbX, tbY etc.. Make the names meaningful! Not only do aliases like tbX have no relation to the original table name (so you encounter tbX, and then have to go searching the rest of the query to find where it's declared so you can say "ah, it's the addresses table") but in this case you join idvclient in twice, but give them unhelpful aliases like tb1, tb3 when really you should have aliased them with something that describes the relationship between them and the rest of the query tables
For example, ParentClient and SubClient or OriginatingClient/HandlingClient would be better names, if these tables are in some relationship with each other.
Whatever the purpose of joining this table in twice is, alias it in relation to the purpose. It may make what you've done wriong easier to spot, for example "oh, of course.. i'm missing a WHERE parentclient.type = 'parent'" (or WHERE handlingclient.handlingdate is not null etc..)
The first step to wisdom is by calling things their proper names

Joining different values in columns, within the same table, into one row

I currently have a table in the following format and with the following example data:
transaction name receipt
1 jim aisjdy
2 john uuyjskl
2 john ioxiqo
3 will eurique
1 jim xoqiel
3 will sijcjg
Each transaction is associated with the customer's name and they receive two separate receipts.
How would one go about creating a query that would display 'transaction', 'name' and the two different receipts in one row?
Example:
transaction name receipt receipt
1 jim aisjdy xoqiel
I know this is bad practice and, in an idea world, I'd have created two columns containing the two different receipts and/or normalized the table into two tables containing transaction and name in one, and receipt 1 and 2 in another. Unfortunately, I did not produce this table and I do not have the time to restructure everything.
I would kindly appreciate any help - thanks in advance!
Since there are only two values, you can use group by:
select transaction, name, min(receipt), max(receipt)
from t
group by transaction, name
It's not pretty, but something like this would work:
SELECT
tr1.transaction,
tr1.name,
tr1.receipt AS receipt1,
tr2.receipt AS receipt2
FROM transaction tr1
JOIN transaction tr2 ON tr1.transaction = tr2.transaction
AND tr1.receipt < tr2.receipt
This works only if there are exactly two records for each transaction, but it would also work if you knew there were always any specific number (you would just join and make sure that tr2.receipt < tr3.receipt, etc). However, as long as there are only two, Gordon's answer is easier to understand and almost certainly faster.

How not to display columns which are NULL in a view

I've set up a view which combines all the data across several tables. Is there a way to write this so that only columns which contain non-null data are displayed, and those columns which contain all NULL values are not included?
ADDED:
Sorry, still studying and working on my first big project so every day seems to be a new experience at the minute. I haven't been very clear, and that's partly because I'm not sure I'm going about things the right way! The client is an academic library, and the database records details of specific collections. The view I mentioned is to display all the data held about an item, so it is bringing together tables on publication, copy, author, publisher, language and so on. A small number of items in the collection are papers, so have additional details over and above the standard bibliographic details. What I didn't want was a user to get all the empty fields relating to papers if what was returned only consisted of books, therefore the paper table fields were all null. So I thought perhaps there would be a way to not show these. Someone has commented that this is the job of the client application rather than the database itself, so I can leave this until I get to that phase of the project.
There is no way to do this in sql.
CREATE VIEW dbo.YourView
AS
SELECT (list of fields)
FROM dbo.Table1 t1
INNER JOIN dbo.Table2 t2 ON t1.ID = t2.FK_ID
WHERE t1.SomeColumn IS NOT NULL
AND t2.SomeOtherColumn IS NOT NULL
In your view definition, you can include WHERE conditions which can exclude rows that have certain columns that are NULL.
Update: you cannot really filter out columns - you define the list of columns that are part of your view in your view definition, and this list is fixed and cannot be dynamically changed......
What you might be able to do is us a ISNULL(column, '') construct to replace those NULLs with an empty string. Or then you need to handle excluding those columns in your display front end - not in the SQL view definition...
The only thing I see you could do is make sure to select only those columns from the view that you know aren't NULL:
SELECT (list of non-null fields) FROM dbo.YourView
WHERE (column1 IS NOT NULL)
and so forth - but there's no simple or magic way to select all columns that aren't NULL in one SELECT statement...
You cannot do this in a view, but you can do it fairly easily using dynamic SQL in a stored procedure.
Of course, having a schema which shifts is not necessarily good for clients who consume the data, but it can be efficient if you have very sparse data AND the consuming client understands the varying schema.
If you have to have a view, you can put a "header" row in your view which you can inspect client-side on the first row in your loop to see if you want to not bother with the column in your grid or whatever, you can do something like this:
SELECT * FROM (
-- This is the view code
SELECT 'data' as typ
,int_col
,varchar_col
FROM TABLE
UNION ALL
SELECT 'hdr' as typ
-- note that different types have to be handled differently
,CASE WHEN COUNT(int_col) = 0 THEN NULL ELSE 0 END
,CASE WHEN COUNT(varchar_col) = 0 THEN NULL ELSE '' END
FROM TABLE
) AS X
-- have to get header row first
ORDER BY typ DESC -- add other sort criteria here
If we're reading your question right, there won't be a way to do this in SQL. The output of a view must be a relation - in (over-)simplified terms, it must be rectangular. That is, each row must have the same number of columns.
If you can tell us more about your data and give us some idea of what you want to do with the output, we can perhaps offer more positive suggestions.
In general, add a WHERE clause to your query, e.g.
WHERE a IS NOT NULL AND b IS NOT NULL AND c IS NOT NULL
Here, a b c are your column names.
If you are joining tables together on potentially NULL columns, then use an INNER JOIN, and NULL values will not be included.
EDIT: I may have misunderstood - the above filters out rows, but you may be asking to filter out columns, e.g. you have several columns and you only want to display columns that contain at least one null value across all the rows you are returning. Using dynamic SQL offers a solution, since the set columns varies depending upon your data.
Here's a SQL query that builds another SQL query containing the appropriate columns. You could run this query, and then submit it's result as another query. It assumes 'pk' is some column that is always non-null, e.g. a primary key - this means we can prefix additional row names with a comma.
SELECT CONCAT("SELECT pk"
CASE (count(columnA)) WHEN 0 THEN '' ELSE ',columnA' END,
CASE (count(columnB)) WHEN 0 THEN '' ELSE ',columnB' END,
// etc..
' FROM (YourQuery) base')
FROM
(YourQuery) As base
The query works using Count(column) - the aggregate function ignores NULL values, and so returns 0 for a column consisting entirely of NULLs. The query builder assumes that YourQuery uses aliases to ensure there no duplicate column names.
While you cant put this into a view, you could wrap it up as a stored procedure that copies the data to another table - the result table. You may also set up a trigger so that the result table is updated whenever the base tables change.
I suspect what's going on is that an end user is running CrystalReports and complaining about all the empty columns that have to be removed manually.
It would actually be possible to create a stored procedure that would create a view on the fly, leaving out dataless columns. But then you would have to run this proc before using the view.
Is that acceptable?

Getting the first of a GROUP BY clause in SQL

I'm trying to implement single-column regionalization for a Rails application and I'm running into some major headaches with a complex SQL need. For this system, a region can be represented by a country code (e.g. us) a continent code that is uppercase (e.g. NA) or it can be NULL indicating the "default" information. I need to group these items by some relevant information such as a foreign key (we'll call it external_id).
Given a country and its continent, I need to be able to select only the most specific region available. So if records exist with the country code, I select them. If, not I want a records with the continent code. If not that, I want records with a NULL code so I can receive the default values.
So far I've figured that I may be able to use a generated CASE statement to get an arbitrary sort order. Something like this:
SELECT *, CASE region
WHEN 'us' THEN 1
WHEN 'NA' THEN 2
ELSE 3
END AS region_sort
FROM my_table
WHERE region IN ('us','NA') OR region IS NULL
GROUP BY external_id
ORDER BY region_sort
The problem is that without an aggregate function the actual data returned by the GROUP BY for a given row seems to be untameable. How can I massage this query to make it return only the first record of the region_sort ordered groups?
Only the first record or only the first group of records? It's not even clear from what you've written whether there is more than one record or not.
In any case, it seems you are bending over backwards to do this in one query, but the database structure is not optimized for this. If there's just 3 levels and you want the most specific, why not just:
SELECT * FROM my_table WHERE region = 'us' GROUP BY external_id
If that returns something then you, stop, otherwise you run 1 or 2 more queries conditionally.
I could be wrong, but my instinct says that will yield much better overall performance, though I suppose it depends on the particulars of your DB.

Search across Columns and replace text

I have an Access database of information where I need to replace text that may reside in 1 of 10 columns. I have a number of different requests for find and replace that need to be done. I need to do this twice a day.
These are the details. We receive a download of data twice a day that has course information in it. A record can have 10 courses in it. Some of the courses need to be combined. For instance
Course 1 is 12345, there are 2 other courses that are the same and therefore course 2(01234), Course 3(34566) all need to be changed to 12345. I also need to combine other course in a similar fashion, since I need to do this twice a day, ideally I would like to have a table with just columns of find and replace and use it to pick up the changes and reference it in my sql code.
An easy way to do this is the key!
Have you considered a cross reference table of something like
Table1
MCourse Subcourse
12345 2(01234)
12345 3(34566)
Then you can do updates like
Set mainTable.Desiredfield = Table1.Mcourse
where desiredfield = subcourse
Or you can create a query that uses the cross reference table to select the desired value and make a new table from that.