TSQL Logic help needed-- Nested IF

  • I am working on implementing a business logic, I need help figuring out the logic

    Logic:

    Each customer has 4 account fileds namely Acc1, Acc2, Acc3 and Acc4 in dbo.Accounts table. It is equally possible for all these accounts to be populated and being NULL, they can be either. Depening on the availability I need to select top 2 accounts as per priority

    Order of Priority is

    Acc1

    Acc3

    Acc2

    Acc4.

    If No account is populated NULL can be selected, If only 1 acc is populated that acc can be slected

    Which means if Acc1 and Acc3 are NOT NULL , we need to SELECT Acc1+'/'+Acc3 FROM dbo.Accounts

    But IF (Acc1 is NULL) , then SELECT Acc3+'/'+Acc2 FROM dbo.Accounts

    And so on . so we will end up in the follwoing possible combinations

    Acc1+Acc3

    Acc1+Acc2

    Acc1+Acc4

    Acc2+Acc3

    Acc2+Acc4

    Acc3+Acc4

    Acc1

    Acc2

    Acc3 and

    Acc4

    Can some one help me with this logic, I can share mine, buts its so Dumb 🙁 . I eneded up in Nested If loops.

    As Im writting this out i have a alternate way of expressing the logic

    IF (All 4 are NULL) --> SELECT NULL

    IF (Only 1 is populated) --> SELECT that 1

    IF (Only 2 are Populated) --> SELECT those 2

    IF (3 or all(4) are populated) --> Apply the priority logic .. Hmmm

    Thnks

    [font="Verdana"]
    Today is the tomorrow you worried about yesterday:-)
    [/font]

  • MV,

    If you're willing to help us help you, I think we can come up with something.

    What I need is for you to read the first article in my signature, then come back and post some table definitions and sample data (in the form of CREATE TABLE / INSERT statements). Then, based upon the sample data provided, show us what your expected results would be. This will allow us to test out different things that will work, and then to post a tested, working solution.

    Off the top of my head, this is looking like:

    1. UNPIVOT.

    2. Sort by priority.

    3. Get the top (2) NOT NULL.

    4. Depending on the expected results, PIVOTing this information back.

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • I appolozize for the unethical post.

    I am atatching three scripts:

    1. TO create the table

    2. TO insert sampel Data

    3. Expected results for about 13 records out of teh selected TOP 100.

    The business logic condition still holds good.

    For the econvinience , the SQL code is being embeded here too

    CREATE TABLE

    /****** Object: Table [dbo].[MasterAccountTest] Script Date: 04/19/2011 16:37:06 ******/

    SET ANSI_NULLS ON

    GO

    SET QUOTED_IDENTIFIER ON

    GO

    SET ANSI_PADDING ON

    GO

    CREATE TABLE [dbo].[MasterAccountTest](

    [Acc1] [numeric](11, 0) NULL,

    [Acc2] [numeric](11, 0) NULL,

    [Acc3] [numeric](11, 0) NULL,

    [Acc4] [numeric](11, 0) NULL)

    GO

    INSERT STATEMENTS

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 2088643245 , 2084297481 , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7507644027 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1670852599 , NULL , 7805021468 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7805026425 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1463229507 , NULL , 7976845259 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7908124714 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1495244131 , NULL , 7771636083 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 2082390898 , 2086875576 , 7960940816 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1792846016 , NULL , 7988507727 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7910815634 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7863212059 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7756071546 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 2380486739 , 2380204745 , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1376503929 , 1376513902 , 7972599274 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1376517366 , 1634255774 , 7824995652 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1946590573 , NULL , 7926416923 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7887597851 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1423877110 , NULL , 7939552884 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1628487832 , NULL , 7852520348 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1249658471 , 1249758425 , 7912205360 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7707093619 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7884004456 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1926772985 , 2476887688 , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1618275378 , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 2086713569 , 2075820111 , 7958749150 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1293401366 , 1293562903 , 7985525286 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1932841155 , 2086435252 , 7956996739 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 2089695507 , 2088852118 , 7910439110 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 1914150804 , 1914914700 , NULL , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7789738640 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7931307726 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , 7909745768 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( 2087651038 , NULL , 7506728549 , NULL );

    INSERT INTO [dbo].[MasterAccountTest] VALUES ( NULL , NULL , NULL , NULL );

    Expected Results

    -- SELECT TOP(100)* FROM dbo.MasterAccountTest

    Acc1 Acc2 Acc3 Acc4 Expected Result

    ------------------------------------------------------- --------------------------------------- --------------------------

    2088643245 2084297481 NULL NULL 2088643245/2084297481

    NULL NULL 7507644027 NULL 7507644027

    1670852599 NULL 7805021468 NULL 1670852599/7805021468

    NULL NULL 7805026425 NULL 7805026425

    1463229507 NULL 7976845259 NULL 1463229507/7976845259

    NULL NULL NULL NULL NULL

    2088643245 2084297481 NULL NULL 2088643245/2084297481

    NULL NULL 7507644027 NULL 7507644027

    1670852599 NULL 7805021468 NULL 1670852599/7805021468

    NULL NULL 7805026425 NULL 7805026425

    1463229507 NULL 7976845259 NULL

    NULL NULL NULL NULL

    NULL NULL 7908124714 NULL

    1495244131 NULL 7771636083 NULL

    2082390898 2086875576 7960940816 NULL

    1792846016 NULL 7988507727 NULL

    NULL NULL 7910815634 NULL

    NULL NULL 7863212059 NULL

    NULL NULL NULL NULL

    NULL NULL NULL NULL

    NULL NULL NULL NULL

    NULL NULL 7756071546 NULL

    2380486739 2380204745 NULL NULL

    1376503929 1376513902 7972599274 NULL

    1376517366 1634255774 7824995652 NULL

    1946590573 NULL 7926416923 NULL

    NULL NULL NULL NULL

    NULL NULL 7887597851 NULL

    1423877110 NULL 7939552884 NULL

    1628487832 NULL 7852520348 NULL

    NULL NULL NULL NULL

    NULL NULL NULL NULL

    NULL NULL NULL NULL

    1249658471 1249758425 7912205360 NULL

    NULL NULL 7707093619 NULL

    NULL NULL 7884004456 NULL

    1926772985 2476887688 NULL NULL

    NULL NULL NULL NULL

    1618275378 NULL NULL NULL

    2086713569 2075820111 7958749150 NULL

    1293401366 1293562903 7985525286 NULL

    1932841155 2086435252 7956996739 NULL

    2089695507 2088852118 7910439110 NULL

    NULL NULL NULL NULL

    1914150804 1914914700 NULL NULL

    NULL NULL 7789738640 NULL

    NULL NULL 7931307726 NULL

    NULL NULL 7909745768 NULL

    2087651038 NULL 7506728549 NULL

    NULL NULL NULL NULL

    1334466649 NULL NULL NULL

    1225445233 NULL 7717084234 NULL

    NULL NULL 7932813594 NULL

    NULL NULL 7852465154 NULL

    NULL NULL NULL NULL

    NULL NULL 7939935894 NULL

    1903828500 NULL 7790401441 NULL

    1179020101 NULL NULL NULL

    1709878878 NULL 7903154402 NULL

    1403282818 NULL 7871567184 NULL

    1233642626 NULL 7936485417 NULL

    2075247884 NULL 7788663030 NULL

    1912857539 NULL 7869223823 NULL

    1912902349 8005515551 7925861087 NULL

    2082861357 NULL 7807088039 NULL

    1386792831 1386793623 7812187883 NULL

    2089547740 2084274343 7718987992 NULL

    NULL NULL 7918778991 NULL

    NULL NULL NULL NULL

    NULL NULL 7872955436 NULL

    NULL NULL 7506853424 NULL

    NULL NULL NULL NULL

    NULL NULL 7974574168 NULL

    1256752000 NULL 7980977186 NULL

    2392343696 NULL NULL NULL

    NULL NULL 7772448207 NULL

    NULL NULL NULL NULL

    1379388287 1379288211 7775913919 NULL

    NULL NULL NULL NULL

    1293573166 NULL 7504246418 NULL

    NULL NULL NULL NULL

    1932771084 NULL 7985740526 NULL

    2838334444 NULL 7926026914 NULL

    NULL NULL 7912606935 NULL

    1614280413 NULL 7923194641 NULL

    2089081551 2089038586 7853357661 NULL

    1773786212 NULL 7854773634 NULL

    NULL NULL NULL NULL

    NULL NULL NULL NULL

    1384867510 NULL NULL NULL

    2079369067 2072085612 NULL NULL

    NULL NULL 7901577217 NULL

    1788514404 NULL 7826431232 NULL

    NULL NULL 7723646997 NULL

    NULL NULL 7923837363 NULL

    1465821414 1456821511 7788897602 NULL

    1536509162 NULL 7842881248 NULL

    1249446842 NULL 7934765817 NULL

    NULL NULL 7933687659 NULL

    NULL NULL 7951268733 NULL

    (100 row(s) affected)

    [font="Verdana"]
    Today is the tomorrow you worried about yesterday:-)
    [/font]

  • Minnesota - Viking (4/19/2011)


    I appolozize for the unethical post.

    I am atatching three scripts:

    1. TO create the table

    2. TO insert sampel Data

    3. Expected results for about 13 records out of teh selected TOP 100.

    The business logic condition still holds good.

    For the econvinience , the SQL code is being embeded here too

    Because of the nature of the formating script on this forum, posting such a huge amount of code really screws things up for the rest of us trying to help. Please go back and edit your post to only contain the first 10 lines of INSERT code. Thanks.

    Here's the code that will solve your problem.

    SELECT STUFF(

    ISNULL('\'+CAST(Acc1 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc2 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc3 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc4 AS VARCHAR(10)),'')

    ,1,1,'')

    FROM dbo.MasterAccountTest

    ;

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Nice solution Jeff - except the priority is supposed to be 1,3,2,4 not 1,2,3,4 according to the orig post, so simple mod required.

    --Edit, I was wrong. In the case where 3 or more accounts are populated, only the first two are required, so the solution requires more work ...

    The absence of evidence is not evidence of absence
    - Martin Rees
    The absence of consumable DDL, sample data and desired results is, however, evidence of the absence of my response
    - Phil Parkin

  • ;

    WITH cteAccountOrder (ColumnName, ColumnOrder) AS

    (

    -- the preferred sorting order when > 2 accounts in a row

    SELECT 'Acc1', 1 UNION ALL

    SELECT 'Acc3', 2 UNION ALL

    SELECT 'Acc2', 3 UNION ALL

    SELECT 'Acc4', 4

    ), cteRN AS

    (

    -- add a row number so we can keep track of each row

    SELECT RN = ROW_NUMBER() OVER (ORDER BY (SELECT 0)),

    Acc1,

    Acc2,

    Acc3,

    Acc4

    FROM MasterAccountTest

    ), cteUnPivot AS

    (

    -- unpivot the acc data into columns of the column name and it's value

    SELECT RN,

    ColumnName,

    ColumnValue

    FROM (SELECT RN, Acc1, Acc2, Acc3, Acc4

    FROM cteRN) p

    UNPIVOT

    (ColumnValue FOR ColumnName IN (Acc1, Acc2, Acc3, Acc4)) AS unpvt

    ), cteSort AS

    (

    -- sort each rows accounts by the preferred sorting order

    SELECT t1.RN,

    t1.ColumnName,

    t1.ColumnValue,

    RNSort = ROW_NUMBER() OVER (PARTITION BY t1.rn ORDER BY t2.ColumnOrder)

    FROM cteUnPivot t1

    JOIN cteAccountOrder t2

    ON t1.ColumnName = t2.ColumnName

    ), cteDistinct AS

    (

    -- get a distinct list of rows

    SELECT DISTINCT RN

    FROM cteSort

    WHERE RNSort < 3

    )

    SELECT c1.RN,

    c2.Accounts

    FROM cteDistinct c1

    -- for each row, get the first (top) two accounts in the proper sort order.

    -- put into a '/' delimited string.

    -- remove the leading '/'

    CROSS APPLY (SELECT Accounts = STUFF((SELECT TOP (2)

    '/' + CONVERT(VARCHAR(11), ColumnValue)

    FROM cteSort c2

    WHERE c1.RN = c2.RN

    ORDER BY c2.RNSort

    FOR XML PATH(''),TYPE).value('.', 'varchar(max)'),1,1,'')) c2

    ORDER BY c1.RN;

    Edit: Check out Creating a comma-separated list (SQL Spackle)[/url] for more information on how the FOR XML/STUFF stuff is all working.

    Wayne
    Microsoft Certified Master: SQL Server 2008
    Author - SQL Server T-SQL Recipes


    If you can't explain to another person how the code that you're copying from the internet works, then DON'T USE IT on a production system! After all, you will be the one supporting it!
    Links:
    For better assistance in answering your questions
    Performance Problems
    Common date/time routines
    Understanding and Using APPLY Part 1 & Part 2

  • Phil Parkin (4/20/2011)


    Nice solution Jeff - except the priority is supposed to be 1,3,2,4 not 1,2,3,4 according to the orig post, so simple mod required.

    --Edit, I was wrong. In the case where 3 or more accounts are populated, only the first two are required, so the solution requires more work ...

    Dang... I didn't even look at the rest of the requirements. Thanks for the catch, Phil.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • @Minnesota - Viking,

    What do you want to do about dupes across the 4 columns?

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • This looked like a fun problem (after Phil told me the rules... thanks again for the catch, Phil) so I decided to give it a whirl in my own fashion, anyway, even after Wayne had sussed the problem. If you consider de-duplification of data a bonus (there's 8 duplicated rows in the data not including the ones with all nulls), I think you may enjoy the performance of the following...

    SELECT --==== Put it all back together from below with a Cross Tab (think pivot)

    Acc1, Acc2, Acc3, Acc4,

    Priority2 =

    MAX(CASE WHEN ca.FinalSort = 1 THEN ca.Accx ELSE NULL END)

    + MAX(CASE WHEN ca.FinalSort = 2 THEN '/' + ca.Accx ELSE '' END)

    FROM dbo.MasterAccountTest

    CROSS APPLY

    ( --=== Get the top 2 non-null entries according to the 1,3,2,4 priority

    SELECT TOP 2

    MySort = ROW_NUMBER() OVER (ORDER BY d.MySort),

    Accx = CAST(d.Accx AS VARCHAR(30))

    FROM ( --=== Stack'em (think unpivot)

    SELECT 1, Acc1 UNION ALL

    SELECT 2, Acc3 UNION ALL

    SELECT 3, Acc2 UNION ALL

    SELECT 4, Acc4

    ) d (MySort,AccX)

    WHERE d.Accx IS NOT NULL

    ) ca (FinalSort,Accx)

    GROUP BY Acc1, Acc2, Acc3, Acc4

    ;

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff's solutions works if you know the length. **a few assumptions here**

    SELECT

    LEFT(

    STUFF(

    ISNULL('\'+CAST(Acc1 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc3 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc2 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc4 AS VARCHAR(10)),'')

    ,1,1,'')

    ,21)

    FROM dbo.MasterAccountTest

    ______________________________________________________________________

    Personal Motto: Why push the envelope when you can just open it?

    If you follow the direction given HERE[/url] you'll likely increase the number and quality of responses you get to your question.

    Jason L. Selburg
  • Jason Selburg (4/20/2011)


    Jeff's solutions works if you know the length. **a few assumptions here**

    SELECT

    LEFT(

    STUFF(

    ISNULL('\'+CAST(Acc1 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc3 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc2 AS VARCHAR(10)),'')

    + ISNULL('\'+CAST(Acc4 AS VARCHAR(10)),'')

    ,1,1,'')

    ,21)

    FROM dbo.MasterAccountTest

    Jason, you're a bloody genius! :blush: Just one tweak on your wonderful idea and we're there!

    If we don't know the length, then we can force the max length for positive BIGINTs and simply remove all spaces afterwards.

    SELECT REPLACE(

    LEFT(

    STUFF(

    ISNULL('\'+CAST(Acc1 AS CHAR(19)),'')

    + ISNULL('\'+CAST(Acc3 AS CHAR(19)),'')

    + ISNULL('\'+CAST(Acc2 AS CHAR(19)),'')

    + ISNULL('\'+CAST(Acc4 AS CHAR(19)),'')

    ,1,1,'')

    ,39)

    ,' ','')

    FROM dbo.MasterAccountTest

    ;

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Neat.

    And there was me trying to find the second occurrence of "\" and SELECT LEFT to that position, dealing with NULLs etc along the way. Not pretty.

    The absence of evidence is not evidence of absence
    - Martin Rees
    The absence of consumable DDL, sample data and desired results is, however, evidence of the absence of my response
    - Phil Parkin

  • Jeff Moden (4/20/2011)


    If we don't know the length, then we can force the max length for positive BIGINTs and simply remove all spaces afterwards.

    Also if you append the separator to the cast instead of prefixing, you don't need the stuff 😛

    Far away is close at hand in the images of elsewhere.
    Anon.

  • David Burrows (4/21/2011)


    Jeff Moden (4/20/2011)


    If we don't know the length, then we can force the max length for positive BIGINTs and simply remove all spaces afterwards.

    Also if you append the separator to the cast instead of prefixing, you don't need the stuff 😛

    Evolution in practice - don't you just love it 😀


    [font="Arial"]Low-hanging fruit picker and defender of the moggies[/font]

    For better assistance in answering your questions, please read this[/url].


    Understanding and using APPLY, (I)[/url] and (II)[/url] Paul White[/url]

    Hidden RBAR: Triangular Joins[/url] / The "Numbers" or "Tally" Table: What it is and how it replaces a loop[/url] Jeff Moden[/url]

  • ChrisM@home (4/21/2011)


    Evolution in practice - don't you just love it 😀

    It is darn near impossible to improve on Jeff's solutions, so must make the most of it while I can 😀

    Far away is close at hand in the images of elsewhere.
    Anon.

Viewing 15 posts - 1 through 15 (of 19 total)

You must be logged in to reply to this topic. Login to reply